Using AJAX in Restlet
How to Use Prototype.js and JSON in Restlet
I will start this tutorial with a sample "microblog", that's a text based blog demonstrating the usage of AJAX in Restlet.
Preparation:
Before we step in,we should review some knowledge if you never know or forget it:
- What's RESTful?
- What's Restlet?
- How to use Restlet?
- How to use db4o to simplify persistence?
- How to use JSON in Prototype.js?
Demo construction:
- Web client: call background service via JSON protocol in RESTful way (GET/PUT/POST/DELETE).
- Server side: uses db4o to work as store service provider, and expose data in RESTful way.
- Server handle process: Application dispatches request to Router, Router finds corresponding reource, Resource handles request and returns representation.
DB4OSimpler.Class:
It's very clean from its name that it works as db4o function simpler.Its generalOperate method handles general operation with db4o:
Note:This class works as only non-concurrent model,because it doesn't work as client/server model.If you have requirement,you should modify it in concurrent(client/server) model by using Db4o.openServer method.
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:
It's a restful Micoblog Server which serves static files and resource(MicroblogResource) and exposes some services(static html and Microblog):
Note:I used TunnelService replacing custom finder,'cause I think TunnelService is easy for using.But I'd discuss how to using custom finder to implement same function.
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:
This is a JavaScript file used in microblog.html file,it call functions which was exposed in server side:
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:
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:
package com.bjinfotech.restlet.practice.demo.microblog;
public class Microblog {
private String subject;
private String content;
private String tags;public String getSubject() {
return subject;
}
...}
Running Application:
Running MicroblogApplication,and visit http://localhost:8182/www/microblog.html.
| Click to enlarge |
Checkout Full Code:
microblog_sourcecode (application/x-zip, 2.3 MB, info)
How to custom Finder to replace TunnelService
Sure, you can custom a finder to do what tunnelService do.
You can visit
http://dobrzanski.net/2007/04/22/using-put-and-delete-methods-in-ajax-requesta-with-prototypejs/
to get detail about how to using restful way in prototype.
A simpler way to do this is to customize the TunnelService. getApplication().getTunnelService().setMethodName("_method"). That's all!
In Application:
...
Router router = new Router(getContext());
//It's very easy!
router.setFinderClass(PrototypeFinder.class);
...
Custom 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 snippet in web page:
...
<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>
...
Thanks
Evgeny Shepelyuk:this guy gave me a lot of good advice!

There are no comments.