Restlet之AJAX实践

cleverpig 发表于 2008-03-04 17:51:44
作者:cleverpig     来源:Matrix社区
评论数:3 点击数:3,434     投票总得分:0 投票总人次:0
关键字:Restlet,AJAX,JSON,Protocol,实践

摘要:

在下面的实践性指南中,我将以一个简单的“微型blog”(microblog)为例详细说明在Restlet中如何使用AJAX技术。。。

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。
image
点击查看放大

下载源代码:

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


本页页面地址:

投票评分(记入本贴作者的专家分)

     非常好 还行 一般 扔鸡蛋          投票总得分: / 投票总人次:

用户评论列表

#1 评论作者: liuwei3230 发表时间: 2008-03-19 09:27 上午

感觉也没什么优势啊...

#2 评论作者: liuwei3230 发表时间: 2008-03-19 09:27 上午

感觉也没什么优势啊...

#3 评论作者: sadasd 发表时间: 2008-04-16 03:25 下午

呵呵,不错足球比分白小姐心水论坛六合采赛马会六合彩香港六合彩香港总彩


发表我的评论 (评论可增加个人积分...)

用户*: E-mail:
评论内容*:

支持BBCode
算术题*: + =