用户的输入总是很有创意的

有时候看似天衣无缝的代码遇到极具创意的用户是不得不低头的

且看下面一段简单的shell代码,检查用户输入如果是合法的文件,然后做一个copy

-r 测试文件存在且可读

-d 检测是否是目录

如果文件存在且可读而且不是目录,(这里不关心文件内容),那么这个输入是否合法呢?

#!/bin/sh
echo "Enter the name of the new licence key file"
echo "licencekeyfile=\c"
read _afile

if [ ! -r $_afile ]
then
	echo "File does not exist"
	exit 1
fi

if [ -d $_afile ]
then
	echo "Licence key file must be a regular file"
	exit 1
fi

#Copy new licenceKey to /var/sog/etc/.licenses/licenseKey
if [ "$_afile" != "/var/sog/etc/.licenses/licenseKey" ]
then
	cp $_afile /var/sog/etc/.licenses/licenseKey
fi

...

用户如何break这个貌似天衣无缝的条件检测的?

Enter the name of the new licence key file
licencekeyfile=/home/user/.licenses/lic*

如果 /home/user/.licenses/lic* 匹配多个文件的情况下,-r 和 -d 判断都能通过,接下来运行到cp 就杯具了

会报下面这个错:


cp: Target /var/sog/etc/.licenses/licenseKey must be a directory
Usage: cp [-f] [-i] [-p] [-@] f1 f2
 cp [-f] [-i] [-p] [-@] f1 ... fn d1
 cp -r|-R [-H|-L|-P] [-f] [-i] [-p] [-@] d1 ... dn-1 dn

真的佩服用户输入的创意啊,为了避免匹配多个文件的情况,不得不用下面的代码来修bug


matches=`ls $_afile | wc -l | sed 's/ //g'`
if [ $matches != 1 ]
then
 echo "Your input of the new licence key file is not valid, it matches $matches files :"
 ls $_afile
 exit 1
fi

My Latest News in 7 Days Vacation

Today is the fifth day of my 7 days vacation.

This was a great holiday for sure.


Having watched the E3 press conferences, I add N3DS to my wish list

Have to say Nintendo press conference was the most successful one compare to Microsoft and SONY.


In Nintendo World:

Still the Nintendo official games was the best ones in its consoles like always.

For me, I almost do not care about the 3rd party games, even Kojima’s Metal Gear Solid, because I can play Metal Gear Solid in another console with amazing screen effects. This point might change later since N3DS offers unique 3D experience now.

Reggie… gorilla …

Miyamoto was not so good at playing Zelda.

Iwata’s English sounds familiar…


In XBOX World:

The new XBOX 360 looks OK, kinda cool, but not still so attractive to me.

Maybe it was caused by the feeling that XBOX games always gets Windows version. (Maybe wrong)

As a matter of fact Microsoft press conference was also great, inherited the fantastic gaming show style, the great games heated the atmosphere.

The Project Natal renamed to Kinect, the full body motion sensor enables you to play with your body, no additional game controllers needed.

I can’t go to E3 to try the Kinect myself, so I have a little bit doubt about the gaming experience.

When will Microsoft design a portable game device?


In SONY World:

PS move did’t bring so much surprise for SONY fans. It is just learning from Wii, plus it gets a stupid light in the front. I don’t like that design.

No new console update, which is reasonable, PS3 is still the most advanced console till now.

In this 3D year, SONY played the card of 3D gaming which is reasonable too, but still feels like not so close to us.


In WorldCup season, everyone is excited, I do watched several matches.

Argentina was the brightest team with the world’s brightest stars.

The two Koreas did played tough role in the field. I think every opponent can never underestimate the two Koreans. 


NBA Final champion finally won by LAKERS this moring!  

Fall behind in the first three quarters, but take the lead in the last quarter.

This was a great show even in the NBA history.


Somthing goes on in my life:

Finally I get the driving license. For the last time the coach ‘Blackmailed’ us 60RMB for its paper fee.

We invite the coach to dinner for teaching us how to drive.

I got so tongue-tied that the coach also brings his concubine – 40-50 year old, heavy makeup, shows cleavage, looks good…

Our coach was definitely an ugly looking guy. Surprisingly unbelievable !!!


I matched a new pair of glasses, when choose the frame I just go for the good looking one, didn’t ask for the price.

When everything was decided, the price scared me a little.

After paid 1211RMB in cash desk, felt like my legs went weak.

This should be an impulsive buying, normally I do not do that.


At last I want to note one important thing, Helen and I choose 35度创意摄影 to take our wedding-photos.

Everything was decided, we just waiting for the day to film, will show photos when it’s ready.

Just I can’t get used to all my moustache shaved.

jQuery Tips : 给AJAX回调函数传递额外参数

讨论这个问题基于如下场景:点击页面上某个按钮之后,触发click事件,事件处理函数内部发送一个AJAX请求,AJAX回调函数更新页面的某一个部分

具体到这个例子,我们希望button1和button2点击之后,用AJAX的方式取example.html的内容,然后动态更新页面的id=callbackdemo3的div

HTML如下: 

<div id="callbackdemo1">
	<button id="button1">ajax load1</button><br/>
</div>
<div id="callbackdemo2">
	<button id="button2">ajax load2</button><br/>
</div>

<div id="callbackdemo3" class="log"></div>

<button onclick="$('.log').html('');">clear</button>

第一种做法:

适用于最简单的情况,也是比较直观的做法,就是在ajax回调函数中,使用jQuery的id选择器$(“#callbackdemo3″)得到id为callbackdemo3的div后更新其HTML内容

$("#callbackdemo1>#button1").click(
function load(){
	$.get("example.html",{ 'param[]': ["var1", "var2"] },
		function f1(data, textStatus, XMLHttpRequest)
		{
			$("#callbackdemo3").html(data);
		}
		);
}
);

 

第二种做法:

定义好一个接受额外参数的回调函数,然后在默认回调函数的内容调用这个预先定义好的回调函数

 这样就能达到传递额外参数的目的了,较之第一种方法,此方法能比较方便的利用各个回调函数的上下文

function callback_with_extraParam(data,param)
{
	param.html(data);
}
$("#callbackdemo2>#button2").click(
function load(){
	var extraParam = $("#callbackdemo3")
	$.get("example.html",{ 'param[]': ["var1", "var2"] },
		function f2(data)
		{
			callback_with_extraParam(data,extraParam);
		}
		);
}
);

 

对于额外参数的重要性,不妨看一个稍微复杂一点点的HTML情况,我们希望更新button3下面的那个div的HTML,这个div没有id,那么如何做呢? 

<div id="callback_complexdemo">
  <button id="button3">ajax load3</button><br/>
  <button onclick="$('div',$(this).closest('div')).html('');">clear</button><br/>
  <div></div>
</div>

当然使用#callback_complexdemo>div也是可以的,但是如果是更加复杂的HTML页面呢?如果没有id=callback_complexdemo呢?嵌套很深呢?

这种情况下,我们就需要有效利用上下文参数这个特性了。

在click的事件处理函数load()中,我们可以方便的获得当前元素的位置var whereAmI = $(this);
这个whereAmI是个jQuery对象,然后在AJAX回调函数f3中,我们利用whereAmI即被点中的这个button想办法去获得想要的div

这一行代码新手有的人看不懂:$(‘div’,$(whereAmI).closest(‘div’))

首先$closest_parent_div = $(whereAmI).closest(‘div’)即查询获得whereAmI最近的父div对象

然后$(‘div’,$closest_parent_div) 即在最近的父div对象这个上下文中,查找内部的div对象

$("#callback_complexdemo>#button3").click(
function load(){
	var whereAmI = $(this);
	$.get("example.html",{ 'param[]': ["var1", "var2"] },
		function f3(data)
		{
			console.log(whereAmI);
			$('div',$(whereAmI).closest('div')).html(data);
		}
		);
}
);

 

 希望通过这个小例子,能让大家学会如何给AJAX回调函数传递额外参数,以及实用且重要的上下文参数。

Cool demo with jQuery plugin FancyBox

代码如下:

<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>

美剧更新自动提醒服务(2)

美剧更新自动提醒服务已更新: http://jhnotifier.appspot.com/

源码host在: http://code.google.com/p/jhnotifier/

确切的说,这个应用目前只能叫做Easytv 更新通知服务,目前仅适合追美剧的有线通用户,当然追国产电视剧也是行的,程序的功能是美剧有更新时,主动发送邮件到你的邮箱实现通知。

现在支持多用户定制自己关心的电视剧,使用邮箱登陆上面的地址后,只要在Step2的表单里填入你关心的easytv剧集的URL地址即可体验,欢迎试用。

计划增加的功能:

1. 短信提醒

2. 支持任意用户,对关心的Easytv美剧提交自己的信息,收到更新通知 Done!

3. 增加其他的爬网页,发通知服务(例如美剧论坛BT种子更新,天气预报,或者是定制程度更高的应用)

如何使用GoogleAppEngine的数据存储服务?

本文描述如果使用GoogleAppEngine的数据存储服务,即如何在GAE上创建,删除,查询数据。重点例举四种类型主键的使用。本文是GAE 文档Creating, Getting and Deleting Data的扩展展开。

代码示例基于 appengine-java-sdk-1.3.3.1 +GAE Eclipse插件

1. 数据存储基础

不管是使用传统的关系型数据库还是使用NOSQL架构的数据存储,我们的应用程序存储实体对象时一般都需要主键,方便于之后的数据查询和更新。

JavaEE架构中使用JPA规范统一POJO在关系型数据库中的映射与持久化。因为JPA主要是为关系型数据库设计的,所以GAE仅支持部分JPA规范的内容,GAE文档里关于JPA的说明也比较简陋。

GAE文档里主要介绍基于JDO的数据存储,JDO规范的好处是它抽象级别更高,并不关心底层的数据库是何种实现,所以对于Google的BigTable架构比JPA适用,当前GAE仅实现了JDO规范里面的部分内容(基于开源的DataNucleus)。

GAE文档里的最简单例子,使用JDO存储数据的4个步骤是

1.1 定义POJO数据类,添加JDO Annotation

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字段上标识该主键值自动填充

1.2 设置JDO

1.3 增强POJO使其与JDO实现相关联

这两步GAE Eclipse插件都帮我们搞定了

1.4 获取PersistenceManager实例操作数据类

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;
    }
}

 

1.4.1 存储数据对象

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();
        }

 

1.4.2 查询获得数据对象

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();

 

2. 使用四种类型主键的实体创建,查询,删除

上文中已经领略最简单的主键的例子,在GAE中所有实体都有一个唯一的标识唯一性的键值。根据文档:一个完整的键包含若干条信息,其中包括应用程序ID类型实体ID

直接读这个描述的话可能不太直观,这三个元素一起才能标识你需要在GAE上存储的实体,其实和JavaEE里配置DataSource+JPA ORM Annotation是类似的,可以这么比较

应用程序ID:相当于关系型数据库的Schema

类型:相当于关系型数据库的表

实体ID:相当于关系型数据库的主键

实体的主键字段可以有四种类型,长整型字符串(未编码字符串),编码字符串形式的主键com.google.appengine.api.datastore.Key 前面两个相对简单常见,比较容易用,后面两个稍微有点绕。

2.1 长整型

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);

 这个查询返回的是实体的列表集合

2.2 字符串

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);接受未编码的字符串为第二个参数用于查询。

2.3 com.google.appengine.api.datastore.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);本质上和上一种未编码字符串是一样的。

2.4 编码字符串形式的主键

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来查询

3. 总结

本文介绍了使用GAE存取数据的基本方法,主要展开并演示了使用4种主键的数据对象存取方法,属于GoogleAppEngine的入门级别文章。

对象关系,事务等相关内容也是构建GAE应用必备的,然而GAE实例代码中并没有很多现成的例子,我瞄到过GAE DataNucleus插件中有些单元测试代码可以参考,因为没有强烈的需求,我还没有深入研究,目前我也只是处于简单的1对1,1对N关系搭起来能用的阶段。

GoogleAppEngine中如何使用EL表达式?

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中的兼容情况