Restlet之AJAX实践
cleverpig 发表于 2008-03-04 17:51:44作者:cleverpig 来源:Matrix社区
评论数:3 点击数:3,434 投票总得分:0 投票总人次:0
关键字:Restlet,AJAX,JSON,Protocol,实践
Restlet的AJAX实践
作者:cleverpig
在下面的实践性指南中,我将以一个简单的“微型blog”(microblog)为例详细说明在Restlet中如何使用AJAX技术。
预备知识:
在编写代码之前,让我们先来回顾一下有关restlet的知识(如果你不曾了解或者忘记的话):
* RESTful是什么?
* 了解Restlet
* 如何使用Restlet?
* Restlet指南
* 如何使用db4o简化持久化数据存储?
* 如何在Prototype.js中使用JSON?
微型blog结构:
* Web client:web浏览器通过JSON协议以RESTful的方式((GET/PUT/POST/DELETE))调用后台服务;
* 服务器端:利用db4o作为数据存储服务提供者,并通过Restlet将数据暴露给web服务;
* 服务端处理过程:在Restlet中,Application接收到Request后将其分配给相应的Router,Router寻找相匹配的Resource类并实例化匹配好的Resource类、将Request传送给它,Resource类对象处理Request并返回相应的表达法(representation)。
DB4OSimpler.Class:
这是一个非常简单的db4o功能封装类,它的generalOperate方法囊括了一些常见的数据操作。
请注意:这个类只是能工作于非并发访问模式,因为它只使用OpenFile的方法简单地打开数据文件进行读写,而这在并发访问时会遇到致命的共享文件锁错误。如果需要处理并发访问的话,请将其修改为Client/Server模式:将Db4o.openFile修改为Db4o.openServer即可。
package com.bjinfotech.util;
import com.db4o.Db4o;
import com.db4o.ObjectContainer;
import com.db4o.query.Query;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.beanutils.*;
/**
* It's very clean from its name that it works as db4o function simpler.
* Its generalOperate method handles general operation with db4o.
* @author cleverpig
*
*/
public class DB4OSimpler {
//operation constants that will be used as generalOperate method's param
public static final int OPERATION_SAVE=0;
public static final int OPERATION_LOAD=1;
public static final int OPERATION_UPDATE=2;
public static final int OPERATION_DELETE=3;
public static final int OPERATION_QUERY=4;
public static final int OPERATION_LIST=5;
public static final int OPERATION_CLEAR=6;
/**
* perform general Operation
* @param fileName
* @param op_code corresponding integer type constant
* @param example object which is needed in operation
* @param keyFieldName object's key field name
* @return
*/
public static Object generalOperate(
String fileName,
int op_code,
Object example,
String keyFieldName){
Object ret=null;
//open db4o file to get ObjectContainer
ObjectContainer db=Db4o.openFile(fileName);
Iterator iter=null;
List list=null;
Query query=null;
try{
//perform operation according to op_code param value
switch(op_code){
case OPERATION_SAVE:
//just set!It's very simple!
db.set(example);
ret=example;
break;
case OPERATION_LOAD:
//just get!
list=db.get(example);
if (list!=null && list.size()>0){
ret=list.get(0);
}
break;
case OPERATION_UPDATE:
//at first,I find objects which will be updated with its key field value
query=db.query();
query.constrain(example.getClass());
query.descend(keyFieldName)
.constrain(BeanUtils.getProperty(example, keyFieldName));
iter=query.execute().listIterator();
//and then delete all of them
while(iter.hasNext()){
db.delete(iter.next());
}
//set new one,now!
db.set(example);
ret=example;
break;
case OPERATION_DELETE:
//just like update process:find firstly,and then delete them
query=db.query();
query.constrain(example.getClass());
query.descend(keyFieldName)
.constrain(BeanUtils.getProperty(example, keyFieldName));
iter=query.execute().listIterator();
if (iter.hasNext()){
while(iter.hasNext()){
db.delete(iter.next());
}
ret=true;
}
else{
ret=false;
}
break;
case OPERATION_QUERY:
//just like update process:find firstly,and then return them
query=db.query();
query.constrain(example.getClass());
query.descend(keyFieldName)
.constrain(BeanUtils.getProperty(example, keyFieldName));
iter=query.execute().listIterator();
list=new ArrayList();
while(iter.hasNext()){
list.add(iter.next());
}
if (list.size()>0)
ret=list;
break;
case OPERATION_LIST:
//return list of object which class is example.class.
list=new ArrayList();
iter=db.query(example.getClass()).listIterator();
while(iter.hasNext()){
list.add(iter.next());
}
if (list.size()>0)
ret=list;
break;
case OPERATION_CLEAR:
//delete anything which class is example.class.
iter=db.query(example.getClass()).listIterator();
int deleteCount=0;
while(iter.hasNext()){
db.delete(iter.next());
deleteCount++;
};
ret=true;
break;
}
//commit,finally
db.commit();
}
catch(Exception ex){
db.rollback();
ex.printStackTrace();
}
finally{
db.close();
}
return ret;
}
}
MicroblogApplication.Class:
这是REST风格的微型blog服务端代码,它提供了静态文件访问、资源(MicroblogResource这个Resource类)的访问服务:
请注意:在这里我使用了TunnelService来代替了自定义finder的方式,因为我认为TunnelService非常易用并且减少开发时间。但我也将在本文后面讨论如何使用自定义finder的方式来实现相同功能。
package com.bjinfotech.restlet.practice.demo.microblog;
import org.restlet.Application;
import org.restlet.Component;
import org.restlet.Directory;
import org.restlet.Restlet;
import org.restlet.Router;
import org.restlet.data.Protocol;
/**
* restful server
* it serves static files and resource
* @author cleverpig
*
*/
public class MicroblogApplication {
public static void main(String[] argv) throws Exception{
Component component=new Component();
//add http protocol
component.getServers().add(Protocol.HTTP,8182);
//add file protocol for accessing static web files in some directories
component.getClients().add(Protocol.FILE);
Application application=new Application(component.getContext()){
@Override
public Restlet createRoot(){
//directory where static web files live
final String DIR_ROOT_URI="file:///E:/eclipse3.1RC3/workspace/RestletPractice/static_files/";
//create router
Router router=new Router(getContext());
//attach static web files to "www" folder
Directory dir=new Directory(getContext(),DIR_ROOT_URI);
dir.setListingAllowed(true);
dir.setDeeplyAccessible(true);
dir.setNegotiateContent(true);
router.attach("/www/",dir);
//attach resource class:MicroblogResource to "/restful/blog" as web service URI
router.attach("/restful/blog",MicroblogResource.class);
return router;
}
};
//use TunnelService to simplify request's dispatching
application.getTunnelService().setEnabled(true);
application.getTunnelService().setMethodTunnel(true);
application.getTunnelService().setMethodParameter("method");
//attach application
component.getDefaultHost().attach(application);
component.start();
}
}
microblogAppInterface.js:
这是用在microblog.html中的JavaScript文件,它以REST的风格调用在服务端暴露的服务:
var SAVE_MODEL=1;
var UPDATE_MODEL=2;
function switchEditorModel(model){
switch(model){
case SAVE_MODEL:
Element.show('save_button');
Element.hide('update_button');
Element.hide('remove_button');
Element.hide('new_button');
break;
case UPDATE_MODEL:
Element.hide('save_button');
Element.show('update_button');
Element.show('remove_button');
Element.show('new_button');
break;
}
}
...
Event.observe(
'save_button',
'click',
function(){
var formObj=Form.serialize('edit_form',true);
var xmlHttp = new Ajax.Request(
"/restful/blog?method=PUT",
{
method: 'post',
parameters: 'json='+encodeURIComponent(Object.toJSON(formObj)),
onComplete: function(transport){
var retObj=transport.responseText.evalJSON();
if (retObj.subject){
alert('ok,"'+retObj.subject+'" was saved!');
refreshBloglist();
switchEditorModel(UPDATE_MODEL);
}
}
}
);
},
false
);
...
function refreshBloglist(){
var xmlHttp = new Ajax.Request(
"/restful/blog",
{
method: 'get',
parameters: '',
onComplete: function(transport){
var retObjs=transport.responseText.evalJSON();
if (retObjs.length && retObjs.length>0){
var listRepr='<ul>\n';
retObjs.each(function(obj,index){
if (index<retObjs.length-1)
listRepr+='<li>'+
'<a href="javascript:load(\''+obj.subject+'\');">'+obj.subject+'</a></li>\n';
});
listRepr+="<\ul>\n";
$('blogList').innerHTML=listRepr;
}
else{
$('blogList').innerHTML='Here is empty';
}
}
}
);
}
function load(subject){
var xmlHttp = new Ajax.Request(
"/restful/blog",
{
method: 'get',
parameters: 'subject='+encodeURIComponent(subject),
onComplete: function(transport){
var retObj=transport.responseText.evalJSON();
if (retObj.subject){
$('subject').value=retObj.subject;
$('content').value=retObj.content;
$('tags').value=retObj.tags;
alert('ok,"'+retObj.subject+'" was loaded!');
switchEditorModel(UPDATE_MODEL);
}
}
}
);
}
MicroblogResource.Class:
这是微型blog资源类。
package com.bjinfotech.restlet.practice.demo.microblog;
import java.util.List;
import java.util.logging.Logger;
import org.restlet.Context;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.StringRepresentation;
import org.restlet.resource.Variant;
import org.restlet.data.Status;
import com.bjinfotech.util.JSONSimpler;
import com.bjinfotech.util.Utils;
public class MicroblogResource extends Resource {
Logger log=Logger.getLogger(MicroblogResource.class.getSimpleName());
//MicroblogPersistenceManager
MicroblogPersistenceManager micoblogPM=new MicroblogPersistenceManager();
//StringRepresentation constant
final StringRepresentation NO_FOUND_REPR=new StringRepresentation("No found!");
final StringRepresentation ERR_REPR=new StringRepresentation("something wrong!");
//json param name in request
final String JSON_PARAM="json";
public MicroblogResource(
Context context,
Request request,
Response response) {
super(context, request, response);
this.getVariants().add(new Variant(MediaType.TEXT_PLAIN));
//it's important,please don't forget it.
this.setAvailable(true);
this.setModifiable(true);
this.setNegotiateContent(true);
}
@Override
/**
* representing after calling default get handle
* @param variant
*/
public Representation represent(Variant variant) throws ResourceException{
log.info("representing after calling default get handle...");
Representation result = null;
if (variant.getMediaType().equals(MediaType.TEXT_PLAIN)) {
//find "subject" param in request,"subject" param is tranformed from web client.it means load one blog with special subject
String subject=getRequest().getResourceRef().getQueryAsForm().getFirstValue("subject");
log.info("subject:"+subject);
//handle query
if (subject!=null && subject.length()>0){
//find blog with special subject
Microblog example=new Microblog();
example.setSubject(subject);
List queryRet=micoblogPM.query(example);
//return result in JSON format StringRepresentation
if (queryRet!=null && queryRet.size()>0){
result=new StringRepresentation(
JSONSimpler.serializeFromBean(queryRet.get(0)),
MediaType.APPLICATION_JSON,
Language.ALL,
CharacterSet.UTF_8
);
}
else{
result=NO_FOUND_REPR;
}
}
else{
//return blog list in JSON format StringRepresentation
result=new StringRepresentation(
JSONSimpler.serializeFromBeanList(micoblogPM.list()),
MediaType.APPLICATION_JSON,
Language.ALL,
CharacterSet.UTF_8
);
}
}
return result;
}
/**
* call MicroblogPersistenceManager's method excluding query and list,just save/update/delete.
* @param method method name
* @param jsonParamVal json param value coming from request
* @return
*/
protected StringRepresentation callMethod(String method,String jsonParamVal){
//transform json format string to Microblog object
Microblog microblog=(Microblog)JSONSimpler.deserializeToBean(jsonParamVal,Microblog.class);
if (microblog!=null){
//call method and gain json format string as responseText
String responseText=Utils.callMethodAndGainResponseJSONStr(
micoblogPM,
method,
jsonParamVal,
Microblog.class);
log.info("response:"+responseText);
//return json StringRepresentation
return new StringRepresentation(
responseText,
MediaType.APPLICATION_JSON,
Language.ALL,
CharacterSet.UTF_8
);
}
else{
return ERR_REPR;
}
}
@Override
/**
* handling post in high level
* @param entity
*/
public void acceptRepresentation(Representation entity) throws ResourceException{
log.info("handling post in high level...");
super.acceptRepresentation(entity);
getResponse().setStatus(Status.SUCCESS_OK);
Form f = new Form(entity);
String jsonParamVal=f.getValues(JSON_PARAM);
log.info("json param:"+jsonParamVal);
//call update and set response
getResponse().setEntity(callMethod("update",jsonParamVal));
}
@Override
/**
* handling put in high level
* @param entity
*/
public void storeRepresentation(Representation entity) throws ResourceException{
log.info("handling put in high level...");
super.storeRepresentation(entity);
getResponse().setStatus(Status.SUCCESS_CREATED);
Form f = new Form(entity);
String jsonParamVal=f.getValues(JSON_PARAM);
log.info("json param:"+jsonParamVal);
//call save and set response
getResponse().setEntity(callMethod("save",jsonParamVal));
}
@Override
/**
* handling delete in high level
* @param entity
*/
public void removeRepresentations() throws ResourceException{
log.info("handling delete in high level...");
super.removeRepresentations();
getResponse().setStatus(Status.SUCCESS_OK);
Form f = getRequest().getEntityAsForm();
String jsonParamVal=f.getValues(JSON_PARAM);
log.info("json param:"+jsonParamVal);
//call delete and set response
getResponse().setEntity(callMethod("delete",jsonParamVal));
}
}
Microblog.Class:
这是描述微型blog对象结构的Microblog.Class。
package com.bjinfotech.restlet.practice.demo.microblog;
public class Microblog {
private String subject;
private String content;
private String tags;
public String getSubject() {
return subject;
}
...
}
运行应用程序:
在eclipse中执行MicroblogApplication,然后打开浏览器访问http://localhost:8182/www/microblog.html。
点击查看放大
下载源代码:
microblog_sourcecode (application/x-zip, 2.3 MB, info)
讨论:如何使用自定义Finder代替TunnelService
的确,我们可以通过自定义Finder来实现tunnelService 的功能。请访问http://dobrzanski.net/2007/04/22/using-put-and-delete-methods-in-ajax-requesta-with-prototypejs/ 来获得更多细节。
修改Application:
...
Router router = new Router(getContext());
//It's very easy!
router.setFinderClass(PrototypeFinder.class);
...
自定义Finder:
public class PrototypeFinder extends Finder {
public PrototypeFinder(Context context, Class<? extends Handler>
targetClass) {
super(context, targetClass);
}
public void handle(Request request, Response response) {
//get "_method" param value
Parameter p = request.getEntityAsForm().getFirst("_method");
//reset requst method accoring "_method" param value
request.setMethod(null != p ? Method.valueOf(p.getValue()) :
request.getMethod());
super.handle(request, response);
}
}
页面中的JavaScript代码片段:
...
<script type="text/javascript" language="JavaScript">
function callJSON() {
new Ajax.Request('/ajax', {
parameters: 'name=PUT', method: 'put', putBody: "PUT BODY",
onComplete: function (transport) {
alert(transport.responseText);
}
});
new Ajax.Request('/ajax', {
parameters: 'name=POST', method: 'post',
onComplete: function (transport) {
alert(transport.responseText);
}
});
new Ajax.Request('/ajax', {
parameters: 'name=DELETE', method: 'delete',
onComplete: function (transport) {
alert(transport.responseText);
}
});
}
</script>
...
感谢:
感谢Evgeny Shepelyuk:这个家伙给我一些好主意!
资源链接:
* Router
* Application
* Resource
* db4o
本页页面地址:


ICP:?B2-20040367