haojii
一个专注于技术的IT男
一个专注于技术的IT男
五 31st
I will hold a workshop of Google App Engine for the next codekata activity, welcome to join me.
Preview:
Click Here: http://jhaddressbook.appspot.com/contact
Slides:
五 21st
代码如下:
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="http://www.haojii.com/webdev/fancyboxdemo/fancybox/jquery.mousewheel-3.0.2.pack.js"></script>
<script type="text/javascript" src="http://www.haojii.com/webdev/fancyboxdemo/fancybox/jquery.fancybox-1.3.1.js"></script>
<link rel="stylesheet" type="text/css" href="http://www.haojii.com/webdev/fancyboxdemo/fancybox/jquery.fancybox-1.3.1.css" media="screen" />
<script type="text/javascript">
$(document).ready(function() {
$("#login").fancybox({
'padding' : 0,
'autoScale' : false,
'transitionIn' : 'none',
'transitionOut' : 'none'
});
$("#flash").fancybox({
'padding' : 0,
'autoScale' : false,
'transitionIn' : 'none',
'transitionOut' : 'none'
});
});
</script>
<div>
<h1><a id="login" href="#register-form" title="This is the title for register form">猛击我吧!</a></h1>
<div style="display:none">
<!-- Registration -->
<div id="register-form" style="width: 500px; height: 300px; margin:auto;">
<h1><a id="flash" href="http://www.adobe.com/jp/events/cs3_web_edition_tour/swfs/perform.swf">A Flash Demo</a></h1>
</div>
</div>
<!-- /Registration -->
</div>
五 20th
美剧更新自动提醒服务已更新: http://jhnotifier.appspot.com/
源码host在: http://code.google.com/p/jhnotifier/
确切的说,这个应用目前只能叫做Easytv 更新通知服务,目前仅适合追美剧的有线通用户,当然追国产电视剧也是行的,程序的功能是美剧有更新时,主动发送邮件到你的邮箱实现通知。
现在支持多用户定制自己关心的电视剧,使用邮箱登陆上面的地址后,只要在Step2的表单里填入你关心的easytv剧集的URL地址即可体验,欢迎试用。
计划增加的功能:
1. 短信提醒
2. 支持任意用户,对关心的Easytv美剧提交自己的信息,收到更新通知 Done!
3. 增加其他的爬网页,发通知服务(例如美剧论坛BT种子更新,天气预报,或者是定制程度更高的应用)
五 6th
本文描述如果使用GoogleAppEngine的数据存储服务,即如何在GAE上创建,删除,查询数据。重点例举四种类型主键的使用。本文是GAE 文档Creating, Getting and Deleting Data的扩展展开。
代码示例基于 appengine-java-sdk-1.3.3.1 +GAE Eclipse插件
不管是使用传统的关系型数据库还是使用NOSQL架构的数据存储,我们的应用程序存储实体对象时一般都需要主键,方便于之后的数据查询和更新。
JavaEE架构中使用JPA规范统一POJO在关系型数据库中的映射与持久化。因为JPA主要是为关系型数据库设计的,所以GAE仅支持部分JPA规范的内容,GAE文档里关于JPA的说明也比较简陋。
GAE文档里主要介绍基于JDO的数据存储,JDO规范的好处是它抽象级别更高,并不关心底层的数据库是何种实现,所以对于Google的BigTable架构比JPA适用,当前GAE仅实现了JDO规范里面的部分内容(基于开源的DataNucleus)。
GAE文档里的最简单例子,使用JDO存储数据的4个步骤是
Employee.java
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private String firstName;
@Persistent
private String lastName;
@Persistent
private Date hireDate;
public Person(String firstName, String lastName, Date hireDate) {
this.firstName = firstName;
this.lastName = lastName;
this.hireDate = hireDate;
}
// Accessors for the fields. JDO doesn't use these, but your application does.
public Long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
// ... other accessors...
}
@PersistenceCapable(identityType = IdentityType.APPLICATION)标识该POJO能够通过JDO存储到数据存储区。
@Persistent标识字段为需要持久存储的
@PrimaryKey标识字段为主键
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)标识在已经被标识为主键的Long字段上标识该主键值自动填充
这两步GAE Eclipse插件都帮我们搞定了
PMF.java
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
public final class PMF {
private static final PersistenceManagerFactory pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
private PMF() {}
public static PersistenceManagerFactory get() {
return pmfInstance;
}
}
import java.util.Date;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import Employee;
import PMF;
// ...
Employee employee = new Employee("Alfred", "Smith", new Date());
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.makePersistent(employee);
} finally {
pm.close();
}
JDO规范的查询格式和JPA的其实差不多
import java.util.List;
import Employee;
// ...
String query = "select from " + Employee.class.getName() + " where lastName == 'Smith'";
List<Employee> employees = (List<Employee>) pm.newQuery(query).execute();
上文中已经领略最简单的主键的例子,在GAE中所有实体都有一个唯一的标识唯一性的键值。根据文档:一个完整的键包含若干条信息,其中包括应用程序ID、类型和实体ID。
直接读这个描述的话可能不太直观,这三个元素一起才能标识你需要在GAE上存储的实体,其实和JavaEE里配置DataSource+JPA ORM Annotation是类似的,可以这么比较
应用程序ID:相当于关系型数据库的Schema
类型:相当于关系型数据库的表
实体ID:相当于关系型数据库的主键
实体的主键字段可以有四种类型,长整型,字符串(未编码字符串),编码字符串形式的主键,com.google.appengine.api.datastore.Key 前面两个相对简单常见,比较容易用,后面两个稍微有点绕。
UserLong.java POJO数据类
import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserLong {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long id;
@Persistent
private Date date = new Date();
@Persistent
private String name;
@Persistent
private String type;
public UserLong() {
}
public UserLong(String name, String type) {
super();
this.name = name;
this.type = type;
}
// ...
单元测试类TestUserWithLongId,对使用长整型字段为主键的对象的创建,查询,删除
import java.util.List;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
import com.haojii.jdo.utils.PMF;
public class TestUserWithLongId {
private static final LocalServiceTestHelper helper = new LocalServiceTestHelper(
new LocalTaskQueueTestConfig(),
new LocalDatastoreServiceTestConfig());
@BeforeClass
public static void setUpBeforeClass() throws Exception {
helper.setUp();
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
helper.tearDown();
}
@Test
public void doTest() {
PersistenceManager pm = PMF.get().getPersistenceManager();
System.out.println("0 Create and persist UserLong with @PrimaryKey: Long id");
UserLong userLong = new UserLong("name1",UserLong.class.getSimpleName());
pm.makePersistent(userLong);
System.out.println("1 Query UserLong with getObjectById");
UserLong tmpUserLong = null;
Long id = userLong.getId();
tmpUserLong = pm.getObjectById(UserLong.class, id);
System.out.println("userLong==tmpUserLong?"+(userLong==tmpUserLong));
Assert.assertTrue(userLong == tmpUserLong);
System.out.println("2 Query UserLong with name1");
tmpUserLong = null;
Query query = pm.newQuery(UserLong.class);
query.setFilter("name == nameParam");
query.setOrdering("date desc");
query.declareParameters("String nameParam");
try {
List<UserLong> results = (List<UserLong>) query.execute("name1");
System.out.println("All UserLong size:"+results.size());
Assert.assertEquals(1, results.size());
if (results.iterator().hasNext()) {
for (UserLong e : results) {
tmpUserLong = e;
System.out.println(" "+e);
}
} else {
System.out.println("2 Query get empty result");
}
} finally {
query.closeAll();
}
Assert.assertTrue(tmpUserLong!=null);
if (tmpUserLong != null)
{
System.out.println("3 Update the searched result[0] UserLong with name1->NAME!");
System.out.println("userLong==tmpUserLong?"+(userLong==tmpUserLong));
Assert.assertTrue(userLong == tmpUserLong);
tmpUserLong.setName("NAME!");
pm.makePersistent(tmpUserLong);
}
System.out.println("4 Delete UserLong with name1");
System.out.println(" ->"+userLong);
pm.deletePersistent(userLong);
query = pm.newQuery(UserLong.class);
int size = ((List<UserLong>) query.execute()).size();
System.out.println("All UserLong size:"+size);
Assert.assertEquals(0, size);
}
}
2.1.1: LocalServiceTestHelper是为GAE本地单元测试提供的辅助类,这里不再赘述,若不明白看一下前面的link的参考文档。
2.1.2: 创建持久化对象pm.makePersistent(entityObject);
2.1.3: 删除持久化对象pm.deletePersistent(entityObject);
2.1.4: 查询长整型主键的对象
方法一,pm.getObjectById(UserLong.class, id)
第一个参数是实体对象的类型,第二个参数是能标识实体的主键这里是长整型的id
方法二,使用JDO查询,可设置查询条件,Query query = pm.newQuery(UserLong.class);
这个查询返回的是实体的列表集合
UserUnEncodedString.java
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserUnEncodedString {
@PrimaryKey
private String name;
@Persistent
private String type;
@Persistent
private Date date = new Date();
public UserUnEncodedString() {
}
public UserUnEncodedString(String name, String type) {
super();
this.name = name;
this.type = type;
}
TestUserWithUnEncodedStringId.java
@Test
public void doTest() {
PersistenceManager pm = PMF.get().getPersistenceManager();
System.out.println("0 Create and persist UserUnEncodedString with @PrimaryKey: String name");
UserUnEncodedString userUnEncodedString = new UserUnEncodedString("key_name1",UserUnEncodedString.class.getSimpleName());
pm.makePersistent(userUnEncodedString);
System.out.println("1 Query UserUnEncodedString with getObjectById");
UserUnEncodedString tmpUserUnEncodedString = null;
String key = userUnEncodedString.getName();
tmpUserUnEncodedString = pm.getObjectById(UserUnEncodedString.class, key);
System.out.println("UserUnEncodedString==tmpUserUnEncodedString?"+(userUnEncodedString==tmpUserUnEncodedString));
Assert.assertTrue(userUnEncodedString==tmpUserUnEncodedString);
System.out.println("2 Query UserUnEncodedString with key_name1");
tmpUserUnEncodedString = null;
Query query = pm.newQuery(UserUnEncodedString.class);
query.setFilter("name == nameParam");
query.setOrdering("date desc");
query.declareParameters("String nameParam");
try {
List<UserUnEncodedString> results = (List<UserUnEncodedString>) query.execute("key_name1");
System.out.println("All UserUnEncodedString size:"+results.size());
Assert.assertEquals(1, results.size());
if (results.iterator().hasNext()) {
for (UserUnEncodedString e : results) {
tmpUserUnEncodedString = e;
System.out.println(" "+e);
}
} else {
System.out.println("2 Query get empty result");
}
} finally {
query.closeAll();
}
Assert.assertTrue(tmpUserUnEncodedString != null);
if (tmpUserUnEncodedString != null)
{
System.out.println("3 Update the searched result[0] UserUnEncodedString with key_name1->KEY_NAME!");
System.out.println("UserUnEncodedString==tmpUserUnEncodedString?"+(userUnEncodedString==tmpUserUnEncodedString));
Assert.assertTrue(userUnEncodedString==tmpUserUnEncodedString);
tmpUserUnEncodedString.setName("KEY_NAME!");
pm.makePersistent(tmpUserUnEncodedString);
}
System.out.println("4 Delete UserUnEncodedString with key_name1");
System.out.println(" ->"+userUnEncodedString);
pm.deletePersistent(userUnEncodedString);
query = pm.newQuery(UserUnEncodedString.class);
int size = ((List<UserUnEncodedString>) query.execute()).size();
System.out.println("All UserUnEncodedString size:"+size);
Assert.assertEquals(0, size);
}
2.2.1 这里也没有特别的地方,值得稍微说一下的是我们使用未编码的字符串作为主键,则需要我们自己保证主键的键值是唯一的,否则makePersistent方法就是更新对象,而不是创建。这个方法pm.getObjectById(UserUnEncodedString.class, key);接受未编码的字符串为第二个参数用于查询。
UserKey.java
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserKey {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private Date date = new Date();
@Persistent
private String name;
@Persistent
private String type;
public UserKey() {
}
public UserKey(String name, String type) {
super();
this.key = KeyFactory.createKey(UserKey.class.getSimpleName(), name);
this.name = name;
this.type = type;
}
2.3.1 首先看这个POJO数据类,主键标识在下面这个字段上Key,参考GAE文档,它的键值包括父实体组(如果有)的键以及应用程序分配的字符串ID或系统生成的数字ID。要创建带应用程序分配的字符串ID的对象,请创建带有该ID的键值并将字段设置为该值。要创建带系统分配的数字ID的对象,请将键字段留为null。
这里我们选择使用程序自己分配字符串ID的Key,使用KeyFactory.createKey(String kind, String name)方法创建Key实例,第一个参数即上文提到的主键组成成分之一类型,第二个参数是标识这个主键的任意唯一的字符串,我的理解是和未编码字符串主键基本上一致。
TestUserWithKeyId.java
@Test
public void doTest() {
PersistenceManager pm = PMF.get().getPersistenceManager();
System.out.println("0 Create and persist UserLong with @PrimaryKey: Key");
UserKey user = new UserKey("name1",UserKey.class.getSimpleName());
pm.makePersistent(user);
System.out.println("1 Query UserKey with getObjectById");
UserKey tmpUser = null;
Key key = user.getKey();
tmpUser = pm.getObjectById(UserKey.class, key);
System.out.println("(1)user==tmpUser ? "+(user==tmpUser));
Assert.assertTrue(user==tmpUser);
Key createdKey = KeyFactory.createKey(UserKey.class.getSimpleName(), user.getName());
tmpUser = pm.getObjectById(UserKey.class, createdKey);
System.out.println("(2)user==tmpUser ? "+(user==tmpUser));
Assert.assertTrue(user==tmpUser);
System.out.println("2 Query UserKey with name1");
tmpUser = null;
Query query = pm.newQuery(UserKey.class);
query.setFilter("name == nameParam");
query.setOrdering("date desc");
query.declareParameters("String nameParam");
try {
List<UserKey> results = (List<UserKey>) query.execute("name1");
System.out.println("All UserKey size:"+results.size());
Assert.assertEquals(1, results.size());
if (results.iterator().hasNext()) {
for (UserKey e : results) {
tmpUser = e;
System.out.println(" "+e);
}
} else {
System.out.println("2 Query get empty result");
}
} finally {
query.closeAll();
}
Assert.assertTrue(tmpUser != null);
if (tmpUser != null)
{
System.out.println("3 Update the searched result[0] UserKey with name1->NAME!");
System.out.println("user==tmpUser?"+(user==tmpUser));
Assert.assertTrue(user==tmpUser);
tmpUser.setName("NAME!");
pm.makePersistent(tmpUser);
}
System.out.println("4 Delete UserKey with name1");
System.out.println(" ->"+user);
pm.deletePersistent(user);
query = pm.newQuery(UserKey.class);
int size = ((List<UserKey>) query.execute()).size();
System.out.println("All UserKey size:"+size);
Assert.assertEquals(0, size);
}
2.3.2 测试用例基本上和前面的一致,这里稍微值得说一下的是使用自己创建的key进行查询Key createdKey = KeyFactory.createKey(UserKey.class.getSimpleName(), user.getName());pm.getObjectById(UserKey.class, createdKey);本质上和上一种未编码字符串是一样的。
UserEncodedString.java
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class UserEncodedString {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")
private String encodedKey;
@Extension(vendorName="datanucleus", key="gae.pk-name", value="true")
private String keyName;
//"gae.pk-name" 字段可以在保存对象前设置为键名称。保存对象时,将使用包括键名称的完整键填充编码键字段。其类型必须为 String。
@Persistent
private Date date = new Date();
@Persistent
private String name;
@Persistent
private String type;
public UserEncodedString() {
}
public UserEncodedString(String keyName, String name, String type) {
super();
this.encodedKey = KeyFactory.keyToString(KeyFactory.createKey(UserEncodedString.class.getSimpleName(), keyName));
this.keyName = keyName;
this.name = name;
this.type = type;
}
2.4.1 这个POJO数据存储类,更绕了一点点弯路,可以和上面的一种使用的Key作为主键的POJO数据类稍微对比一下看,然后就不会觉得太复杂了。
正如注释中所写
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@Extension(vendorName=”datanucleus”, key=”gae.encoded-pk”, value=”true”)
private String encodedKey;
@Extension(vendorName=”datanucleus”, key=”gae.pk-name”, value=”true”)
private String keyName;
//”gae.pk-name” 字段可以在保存对象前设置为键名称。保存对象时,将使用包括键名称的完整键填充编码键字段。其类型必须为 String。
this.encodedKey = KeyFactory.keyToString(KeyFactory.createKey(UserEncodedString.class.getSimpleName(), keyName));
keyName字段只是用来产生使用程序自己分配字符串ID的Key的,根据这个字符串ID产生Key之后,将Key的编码字符串格式保存于encodedKey当中。
我的理解是可以encodedKey的作用==Key的作用,只是为了某些情况下使用方便。
TestUserWithEncodedStringId.java
@Test
public void doTest() {
PersistenceManager pm = PMF.get().getPersistenceManager();
System.out.println("0 Create and persist UserLong with @PrimaryKey: String encodedKey");
UserEncodedString userEncodedString = new UserEncodedString("name1","name1",UserEncodedString.class.getSimpleName());
pm.makePersistent(userEncodedString);
System.out.println("1 Query UserEncodedString with getObjectById, using KeyFactory.stringToKey(encodedKey)");
UserEncodedString tmpUserEncodedString = null;
String encodedKey = userEncodedString.getEncodedKey();
System.out.println(userEncodedString);
Key key = KeyFactory.stringToKey(encodedKey);
System.out.println("KeyFactory.stringToKey(encodedKey)="+key);
tmpUserEncodedString = pm.getObjectById(UserEncodedString.class, key);
System.out.println("UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
Assert.assertTrue(userEncodedString==tmpUserEncodedString);
System.out.println("1(1) Query UserEncodedString with getObjectById, using KeyFactory.createKey(UserEncodedString.class.getSimpleName(), userEncodedString.getKeyName())");
key = KeyFactory.createKey(UserEncodedString.class.getSimpleName(), userEncodedString.getKeyName());
tmpUserEncodedString = pm.getObjectById(UserEncodedString.class, KeyFactory.keyToString(key));
System.out.println(" (1)UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
Assert.assertTrue(userEncodedString==tmpUserEncodedString);
System.out.println("1(2) Query UserEncodedString with getObjectById, using userEncodedString.getEncodedKey():"+userEncodedString.getEncodedKey());
tmpUserEncodedString = pm.getObjectById(UserEncodedString.class, userEncodedString.getEncodedKey());
System.out.println(" (2)UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
Assert.assertTrue(userEncodedString==tmpUserEncodedString);
System.out.println("2 Query UserEncodedString with name1");
tmpUserEncodedString = null;
Query query = pm.newQuery(UserEncodedString.class);
query.setFilter("name == nameParam");
query.setOrdering("date desc");
query.declareParameters("String nameParam");
try {
List<UserEncodedString> results = (List<UserEncodedString>) query.execute("name1");
System.out.println("All UserEncodedString size:"+results.size());
Assert.assertEquals(1, results.size());
if (results.iterator().hasNext()) {
for (UserEncodedString e : results) {
tmpUserEncodedString = e;
System.out.println(" "+e);
}
} else {
System.out.println("2 Query get empty result");
}
} finally {
query.closeAll();
}
Assert.assertTrue(tmpUserEncodedString != null);
if (tmpUserEncodedString != null)
{
System.out.println("3 Update the searched result[0] UserEncodedString with name1->NAME!");
System.out.println("UserEncodedString==tmpUserEncodedString?"+(userEncodedString==tmpUserEncodedString));
Assert.assertTrue(userEncodedString==tmpUserEncodedString);
tmpUserEncodedString.setName("NAME!");
pm.makePersistent(tmpUserEncodedString);
}
System.out.println("4 Delete UserEncodedString with name1");
System.out.println(" ->"+userEncodedString);
pm.deletePersistent(userEncodedString);
query = pm.newQuery(UserEncodedString.class);
int size = ((List<UserEncodedString>) query.execute()).size();
System.out.println("All UserEncodedString size:"+size);
Assert.assertEquals(0, size);
}
2.4.2 测试用例还是和前面的一致,这里要说的就是下面三个使用getObjectById的查询结果是一致的。
pm.getObjectById(UserEncodedString.class, userEncodedString.getEncodedKey());
第一个使用Key的编码字符串格式直接查询
String encodedKey = userEncodedString.getEncodedKey();
Key key = KeyFactory.stringToKey(encodedKey);
pm.getObjectById(UserEncodedString.class, key);
第二个使用KeyFactory.stringToKey方法根据key查询
key = KeyFactory.createKey(UserEncodedString.class.getSimpleName(), userEncodedString.getKeyName());
pm.getObjectById(UserEncodedString.class, KeyFactory.keyToString(key));
第三个使用程序自己分配字符串ID生成的Key来查询
本文介绍了使用GAE存取数据的基本方法,主要展开并演示了使用4种主键的数据对象存取方法,属于GoogleAppEngine的入门级别文章。
对象关系,事务等相关内容也是构建GAE应用必备的,然而GAE实例代码中并没有很多现成的例子,我瞄到过GAE DataNucleus插件中有些单元测试代码可以参考,因为没有强烈的需求,我还没有深入研究,目前我也只是处于简单的1对1,1对N关系搭起来能用的阶段。
四 28th
GoogleAppEngine中如何使用EL表达式?
方法是必须在页面里强制指定:
<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″ isELIgnored=”false”%>
我也尝试在web.xml中配置了el-ignore:false,但是这样做不行。
This doesn't work for GAE <jsp-config> <jsp-property-group> <url-pattern>/*</url-pattern> <el-ignored>false</el-ignored> </jsp-property-group> </jsp-config>
除此之外,还要在WEB-INF/lib中加入JSTL,EL的jar文件。
update:自己加入JSTL和EL的jar文件可能会导致这个问题 java.lang.AbstractMethodError: javax.servlet.jsp.PageContext.getELContext()Ljavax/el/ELContext;
但是如果不加入,使用eclipse编辑jsp的时候不能对JSTL进行代码辅助提示,权衡一下把jar加入classpath后运行app时还需要去掉,否则会有兼容性问题。
有用的参考资料:JavaEE的相关技术在Google App Engine中的兼容情况。
四 22nd
本文描述”Rate limit exceeded. Clients may not make more than 150 requests per hour” 的解决办法
昨天做空姐的twitter自动翻译+Retweet程序的时候遇到的问题(使用GAE+twitter4j+google translate API)
调用twitter4j API去查询空姐最新状态的时候,一直报这个错 “Rate limit exceeded. Clients may not make more than 150 requests per hour”,月光博客也遇到过这个问题,认为这个是GAE IP不是白名单的问题,难道没有解决办法了?
有很多基于GAE的twitter应用,如果大家都这样那岂不是要疯掉了。。。所以我想肯定不关白名单啥事情,一定有别的解决办法。
后来我在google code里查到一条类似的issue ,幸运的是已经fix了
所以这个问题解决办法就是:修改searchURL为 http://api.twitter.com/1/search.json (实效性不能保证,至少目前这个方法works)
如果遇到问题的各位童鞋也用的是twitter4j,大家会发现twitter4j jar已经封装好了,默认的searchBaseURL是http://search.twitter.com/search.json,我看了一下twitter4j的源码,对于[基于GAE的]web项目,我们可以在WEB-INF/下面新建一个twitter4j.properties,然后增加这一行searchBaseURL=http://api.twitter.com/1/
四 19th
小路抽到限宽门,上车的时候倒是蛮自信的
第一次侧方停车,语音提示说考试结束,我晕,看提示屏幕说后车身出线
开过去再来一次,又停了一次侧方,这次没语音提示了,然后一路开到底没啥问题,结果没有提示通过,原来这一次侧方没成绩
又开过去补一次侧方停车,停的感觉蛮好的,拉手刹准备开出去的时候,又TMD的叫了,考试结束
郁闷,严重怀疑考试系统有问题,自己感觉的确停的很好啊
心情很不爽,开出去了,后来想想真后悔自己没有下车看看到底有没有后车身出线
唉,鄙视自己挂在侧方停车上了,太鄙视自己了
四 15th
原文地址:20 Helpful jQuery Methods you Should be Using
原文作者:Andrew Burgess
这篇文章介绍了jQuery中有用的20个方法,1-20编号如下,本文没有对照原文逐句翻译,这个20个方法名称,我其实就是用下面第14个map()的方式取出来的,亮点如下,firebug真是个离不开的好工具。
说到工具,那就顺便再提一下一个简洁的jQuery代码调试工具:jQueryPad
支持代码高亮,不支持代码辅助,小问题是这个工具带的jQuery.js的版本旧了,需要自己更新一份到它的目录。
(当然,我还是最喜欢firebug,特别是网页复杂的时候)
这个无需多说了,上面的图就是个例子,就是在当前对象的前面或者后面插入内容,被插入的内容是当然对象的兄弟节点
和click()或者hover()一样,它也是个事件处理器,这个change事件作用于 textarea, text input, select, 当目标元素的值改变的时候触发该事件。它不同于对象失去焦点时触发的事件foucusOut()和blur()。
change()事件对于客户端验证的任务最适合不过了,因为你不需要在输入字段值没有变化的时候重新验证字段。
$(document).ready(
function() {
$('input[type=text]').change(function () {
switch (this.id) {
/* some validation code here */
case "name":
alert(this.value+"....");
break;
default:
alert(this.id);
break;
}
});
});
// HTML Form
Name: <input id="name" type="text"/>
在jQuery中,使用选择器,其实默认作用于document这个上下文上
了解了这一点话,Context就简单了,它是一个属性兼参数
作为属性:每个jQuery对象,都有个context属性
作为参数:当使用jQuery选择器时,可以使用第二个参数context,细化了查询的范围
用Firebug试验一下就明白了!
在元素上(确切的说应该是jQuery对象)上存储数据的一种办法
用在jQuery动画效果上,可以方便的增加或者移除一个特效
暂停一段时间的动画效果
以前的文章说过了,不废话了
用数组下标的方式取得一组元素中的某一个
取得jQuery元素对应的Dom对象格式
这个我挺喜欢,第二个参数是个用于过滤的回调函数,很容易懂的
var nums = '1,2,3,4,5,6,7,8,9,10'.split(',');
nums = $.grep(nums, function(num, index) {
// num = the current value for the item in the array
// index = the index of the item in the array
return num > 5; // returns a boolean
});
console.log(nums) // 6,7,8,9,10
$(':animated'); // returns all elements currently animating
$(':contains(me)'); // returns all elements with the text 'me'
$(':empty'); // returns all elements with no child nodes or text
$(':parent'); // returns all elements with child nodes or text
$('li:even'); // returns all even-index elements (in this case, <li>s)
$('li:odd'); // can you guess?
$(':header'); // returns all h1 - h6s.
$('li:gt(4)'); // returns all elements with an (zero-based) index greater than the given number
$('li:lt(4)'); // returns all element with an index less than the given number
$(':only-child'); // returns all . . . well, it should be obvious
$.isArray([1, 2, 3]); // returns true
$.isEmptyObject({}); // returns true
$.isFunction(function () { /****/ }); // returns true
function Person(name) {
this.name = name
return this;
}
$.isPlainObject({})); // returns true
$.isPlainObject(new Object()); // returns true
$.isPlainObject(new Person()); // returns false
使用jQuery选择器返回的都是jQuery对象,所以$.makeArray()变得挺实用的
var ps = $('p');
$.isArray(ps); //returns false;
ps = $.makeArray(ps);
$.isArray(ps); // returns true;
上面firebug的贴图中延伸过了,自己试试看吧
把JSON格式的字符串结果解析成JSON对象
$.proxy() 解决了函数调用的上下文问题
var person = {
name : "Andrew",
meet : function () {
alert('Hi! My name is ' + this.name);
}
};
person.meet(); // 正常工作
$('#test').click(person.meet); // 返回 Hi! My name is undefined 或者 Hi! My name is 空
$('#test').click($.proxy(person.meet, person));
// we could also do $.proxy(person, "meet")
用来替换内容的,replaceWith更顺脑
<div class="error1">error section 1</div>
<br/>
<div class="error2">error section 2</div>
<br/>
$('<span class=fixed>The error has been corrected</span>').replaceAll('.error1');
"$('.error2').replaceWith('<span class=fixed>The error has been corrected</span>');
看例子吧,易懂
<form>
<input type="text" name="name" value="John Doe" />
<input type="text" name="url" value="http://www.example.com" />
</form>
console.log($('form').serialize()); // logs : name=John+Doe&url=http%3A%2F%2Fwww.example.com
console.log($('form').serializeArray());
// logs : [{ name : 'name', value : 'John Doe'} , { name : 'url', value : 'http://www.example.com' } ]
返回所有兄弟节点
在作用对象的外面,周围,或者里面,包一层东西
好好玩这些个方法吧!
…
四 6th
美剧自动提醒服务: http://jhnotifier.appspot.com/
源码host在: http://code.google.com/p/jhnotifier/
设想是做成一个通用的通知系统框,基于这个框架可以开发出很多爬网页发通知(邮件,短信)的应用
当前这个服务可以配置关心的Easytv上的美剧更新,但是仅支持向我自己的邮箱发邮件提醒
计划增加的功能:
1. 短信提醒
2. 支持任意用户,对关心的Easytv美剧提交自己的信息,收到更新通知
3. 增加其他的爬网页,发通知服务(例如美剧论坛BT种子更新,天气预报,或者是定制程度更高的应用)
欢迎有兴趣的加入这个项目,重构代码,加入更多的提醒服务!
系统主要结构也比较简单:
所有的爬网页任务实现ObservableTask接口,在doTask方法里实现逻辑,notify Observer
程序预先实现好的Observer有:Email通知,SMS通知(暂时只是框架代码)
public abstract class ObservableTask extends Observable{
public abstract void doTask() throws Exception;
}
public class EmailNotifier extends Notifiable implements Observer {
private static final Logger log = Logger.getLogger(EmailNotifier.class.getName());
public void update(Observable o, Object arg) {
for (User user : this.getNotifierList()) {
AppEngineEmailSender.send(user.email, ((Entity)arg).emailSubject(), ((Entity)arg).emailBodyText());
}
}
}
做Easytv这个提醒服务的时候,开始是基于CLI的,后来玩了一下Google App Engine,目前对它的logging还不是100%清楚,不知道它怎么映射日志级别的,然后还有点数据存储服务的代码,一对多玩起来了,但是不全明白,貌似和EJB3还是有点不一样,有空了再玩吧!