Showing posts with label Servlet. Show all posts
Showing posts with label Servlet. Show all posts

Saturday, October 26, 2013

Do Something on Server Startup/Shutdown


Simple Note

Code

TestServletContextListener.java

Simply output message to console while server startup/shutdown.

package test;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class TestServletContextListener implements ServletContextListener {
    // do something on server shutdown
    @Override
    public void contextDestroyed(ServletContextEvent arg0) {
        System.out.println("On server shutdown");
    }
    // do something on server startup
    @Override
    public void contextInitialized(ServletContextEvent arg0) {
        System.out.println("On server startup");
    }
}


web.xml

Define listener.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>DoStOnServerStartupOrShutdown</display-name>
  <!-- Define Listener -->
  <listener>
    <listener-class>test.TestServletContextListener</listener-class>
  </listener>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
</web-app>


References

Servlet Filters and Event Listeners
http://docs.oracle.com/cd/B14099_19/web.1012/b14017/filters.htm

Debug ServletContextListener.contextDestroyed() by setting the breaking point in eclipse
http://stackoverflow.com/questions/8802457/debug-servletcontextlistener-contextdestroyed-by-setting-the-breaking-point-in

Download

Full project at github
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/BasicServletPractice/Projects/DoStOnServerStartupOrShutdown

Saturday, September 7, 2013

ZK AURequest with WebSocket


Introduction

This article describe how to use WebSocket to process Request/Response in ZK.

NOTE: This is just a POC with customized ZK timer and intbox

Environment: Tomcat 7.0.42, ZK 6.5.2

Result

View demo on line
http://screencast.com/t/J6czUwBia0CI

Pre-request

Simple WebSocket Test with Tomcat
http://ben-bai.blogspot.tw/2013/07/simple-websocket-test-with-tomcat.html

ZK: Override Widget in zk.xml
http://ben-bai.blogspot.tw/2013/07/zk-override-widget-in-zkxml.html

Group Connections with WebSocket
http://ben-bai.blogspot.tw/2013/07/group-connections-with-websocket.html

Program

index.zul

Use customized timer and intbox in it.

<zk>
    <!-- Tested with ZK 6.5.2 -->
    <div apply="test.TestComposer">
        <!-- use custom timer and intbox -->
        <timer id="timer"
            repeats="true" running="true" delay="1000"
            use="components.Timer"
            useWebSocketAU="true" />
        <timer id="timer2"
            repeats="true" running="true" delay="500"
            use="components.Timer"
            useWebSocketAU="true" />
        <intbox id="ibx" value="0"
            use="components.Intbox"
            useWebSocketAU="true" />
        <intbox id="ibx2" value="0"
            use="components.Intbox"
            useWebSocketAU="true" />
    </div>
</zk>


TestComposer.java

Composer for test page, register EventListener for WebSocket to component in it.

package test;


import impl.EventListener;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Wire;

import components.IWebSocketEnhancedComponent;
import components.Intbox;
import components.Timer;

/** Tested with ZK 6.5.2
 * 
 * @author benbai123
 *
 */
public class TestComposer extends SelectorComposer<Component> {

    private static final long serialVersionUID = -5014610291543614202L;
    // custom timer and intbox
    @Wire
    Timer timer;
    @Wire
    Timer timer2;
    @Wire
    Intbox ibx;
    @Wire
    Intbox ibx2;

    public void doAfterCompose (Component comp) throws Exception {
        super.doAfterCompose(comp);
        registerEventListenerForWebSocketEnhancedTimer();
    }

    /** Register EventListener for WebSocket,
     * Events.postEvent cannot work with WebSocket (without HttpRequest),
     * so cannot use @Listen in Composer
     * 
     */
    protected void registerEventListenerForWebSocketEnhancedTimer () {
        // cast to IWebSocketEnhancedComponent
        IWebSocketEnhancedComponent enhancedTimer = (IWebSocketEnhancedComponent)timer;
        // register event listener for onTimer event of timer
        enhancedTimer.registerListenerForWebSocketEvent("onTimer",
            new EventListener () {
                private static final long serialVersionUID = -8920291597084200994L;

                public void onEvent (Event event) {
                    // increase value of intbox
                    ibx.setValue(ibx.getValue() + 1);
                }
            }
        );
        enhancedTimer = (IWebSocketEnhancedComponent)timer2;
        // register event listener for onTimer event of timer2
        enhancedTimer.registerListenerForWebSocketEvent("onTimer",
            new EventListener () {
                private static final long serialVersionUID = -5721055473084949736L;

                public void onEvent (Event event) {
                    // increase value of intbox
                    ibx2.setValue(ibx2.getValue() - 1);
                }
            }
        );
    }
}


IWebSocketEnhancedComponent.java

Define a "WebSocket Enhanced Component"

package components;

import impl.EventListener;
import impl.RequestFromWebSocket;

/**
 * Define a "WebSocket Enhanced Component"
 * for request-response pattern
 * 
 * @author benbai123
 *
 */
public interface IWebSocketEnhancedComponent {
    /**
     * Whether use WebSocket to process AU request
     */
    public void setUseWebSocketAU (boolean useWebSocketAU) ;
    /**
     * Help desktop to register itself into session
     * since we need it in WebSocket and we will not
     * use custom desktop for this POC
     */
    public void helpDesktopToRegister () ;
    /**
     * Process event sent from client
     * @see TestWebSocketServlet.TestMessageInbound#onTextMessage(java.nio.CharBuffer)
     */
    public void serviceWebSocket (RequestFromWebSocket request) ;
    /**
     * Update status to client
     */
    public void addUpdateProp (String prop, String value) ;
    /**
     * Register Event Listener at Component itself since
     * Events.postEvent cannot work with WebSocket without 
     * an active Execution, need to register listener manually in
     * Composer
     * @param listener EventListener used to process event from WebSocket request
     */
    public void registerListenerForWebSocketEvent (String evtnm, EventListener listener) ;
}


Timer.java

Implement IWebSocketEnhancedComponent as needed, process custom EventListener with onTimer Event.

package components;

import impl.DesktopUtils;
import impl.EventListener;
import impl.RequestFromWebSocket;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.zkoss.zk.ui.event.Event;

public class Timer extends org.zkoss.zul.Timer implements IWebSocketEnhancedComponent {

    private static final long serialVersionUID = 1006786038089103071L;
    private boolean _useWebSocketAU;
    // hold event listener, put it here since
    // we will not use customized AbstractComponent in this POC
    private Map<String, List<EventListener>> _listenerForWebSocket = new HashMap<String, List<EventListener>>();;

    @Override
    public void setUseWebSocketAU (boolean useWebSocketAU) {
        if (_useWebSocketAU != useWebSocketAU) {
            _useWebSocketAU = useWebSocketAU;
            smartUpdate("useWebSocketAU", useWebSocketAU);
        }
    }

    @Override
    public void helpDesktopToRegister () {
        if (getDesktop() != null) {
            // register desktop
            DesktopUtils.register(getDesktop());
        }
    }

    // called by TestWebSocketServlet.TestMessageInbound#onTextMessage(java.nio.CharBuffer)
    @Override
    public void serviceWebSocket(RequestFromWebSocket request) {
        String command = request.getCommand();
        if ("onTimer".equals(command)) {
            for (EventListener l : _listenerForWebSocket.get("onTimer")) {
                l.onEvent(new Event("onTimer", this, null));
            }
        }
    }

    @Override
    public void addUpdateProp (String prop, String value) {
        // ignore, not used in this POC
    }

    // called by TestComposer
    /**
     * register event listener with specific event name
     */
    @Override
    public void registerListenerForWebSocketEvent (String evtnm, EventListener listener) {
        // try to get listener list with respect to specified event name
        List<EventListener> listeners = _listenerForWebSocket.get(evtnm);
        if (listeners == null) {
            // create new if list is not exists
            listeners = new ArrayList<EventListener>();
            _listenerForWebSocket.put(evtnm, listeners);
        }
        // add event listener into listener list
        listeners.add(listener);
    }

    // render useWebSocketAU as needed
    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
        throws java.io.IOException {
        super.renderProperties(renderer);
        if (_useWebSocketAU) {
            helpDesktopToRegister();
            // render useWebSocketAU to client side
            render(renderer, "useWebSocketAU", _useWebSocketAU);
        }
    }
}


Intbox.java

Implement IWebSocketEnhancedComponent as needed, override setValue to update status to client by WebSocket if needed.

package components;

import impl.DesktopUtils;
import impl.EventListener;
import impl.RequestFromWebSocket;

public class Intbox extends org.zkoss.zul.Intbox implements IWebSocketEnhancedComponent {

    private static final long serialVersionUID = -6488494817604420277L;
    private boolean _useWebSocketAU;

    @Override
    public void setUseWebSocketAU (boolean useWebSocketAU) {
        if (_useWebSocketAU != useWebSocketAU) {
            _useWebSocketAU = useWebSocketAU;
            smartUpdate("useWebSocketAU", useWebSocketAU);
        }
    }

    @Override
    public void helpDesktopToRegister () {
        if (getDesktop() != null) {
            // register desktop
            DesktopUtils.register(getDesktop());
        }
    }

    @Override
    public void serviceWebSocket(RequestFromWebSocket request) {
        /* ignore, not used in this POC */
    }

    @Override
    public void addUpdateProp (String prop, String value) {
        // add prop/value to update
        DesktopUtils.updateComponentProp(this, prop, value);
    }

    @Override
    public void registerListenerForWebSocketEvent (String evtnm, EventListener listener) {
        /* ignore, not used in this POC */
    }

    // render useWebSocketAU as needed
    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer)
        throws java.io.IOException {
        super.renderProperties(renderer);
        if (_useWebSocketAU) {
            helpDesktopToRegister();
            // render useWebSocketAU to client side
            render(renderer, "useWebSocketAU", _useWebSocketAU);
        }
    }
    public void setValue (int value) {
        if (_useWebSocketAU) { // use WebSocket to process AU request?
            // set value without smartUpdate
            super.setValueDirectly(value);
            // update client status via WebSocket
            addUpdateProp("value", value+"");
        } else {
            // original function
            super.setValue(value);
        }
    }
}


EventListener.java

Used to process event in this POC.

package impl;

import java.io.Serializable;

import org.zkoss.zk.ui.event.Event;

/**
 * Define an Event listener used to process event from
 * WebSocket request
 * 
 * @author benbai123
 *
 */
public interface EventListener extends Serializable {
    /** Called when the registered event is triggered
     * 
     * @param event
     * @see TestComposer#registerEventListenerForWebSocketEnhancedTimer()
     */
    public void onEvent (Event event) ;
}


RequestFromWebSocket.java

Used to create a request object for WebSocket request message.

package impl;

import java.util.Map;

import org.zkoss.zk.ui.Component;

public class RequestFromWebSocket {
    /** Event name */
    private String _command;
    /** Target component */
    private Component _comp;
    /** Data map */
    private Map<Object, Object> _data;
    // Constructor
    public RequestFromWebSocket (String command, Component comp, Map<Object, Object> data) {
        _command = command;
        _comp = comp;
        _data = data;
    }
    // getters
    public String getCommand () {
        return _command;
    }
    public Component getComponent () {
        return _comp;
    }
    public Map<Object, Object> getData () {
        return _data;
    }
}


DesktopUtils.java

Utility of desktop, used to register desktop, store status to update, build response.

package impl;

import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpSession;

import org.zkoss.json.JSONObject;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Sessions;

public class DesktopUtils {
    public static final String ATTRIBUTES_TO_UPDATE = "ATTRIBUTES_TO_UPDATE";
    public static final String REGISTERED_DESKTOPS = "REGISTERED_DESKTOPS";
    public static final String ALREADY_REGISTERED = "ALREADY_REGISTERED";

    public static void updateComponentProp (Component comp, String prop, String val) {
        String id = comp.getUuid();

        getPropMap(comp.getDesktop(), id).put(prop, val);
    }
    /** Register desktop so we can try to find it when
     * WebSocket receive AU request from client side
     */
    public static void register (Desktop desktop) {
        if (desktop.getAttribute(ALREADY_REGISTERED) == null) {
            getRegisteredDesktops(null).put(desktop.getId(), desktop);
            desktop.setAttribute(ALREADY_REGISTERED, ALREADY_REGISTERED);
        }
    }
    /** Get registered desktop
     * 
     * @param sess HttpSession
     * @param id desktop ID
     * @return Desktop
     */
    public static Desktop getRegisteredDesktop (HttpSession sess, String id) {
        return getRegisteredDesktops(sess).get(id);
    }
    /** Remove registered desktop
     * 
     * @param sess HttpSession
     * @param id desktop ID
     */
    public static void removeRegisteredDesktop (HttpSession sess, String id) {
        getRegisteredDesktops(sess).remove(id);
    }
    /** Get Map for registered desktops
     * 
     * @param sess HttpSession
     * @return Map<String, Desktop>
     */
    @SuppressWarnings("unchecked")
    private static Map<String, Desktop> getRegisteredDesktops (HttpSession sess) {
        Map<String, Desktop> registeredDesktops = null;
        if (sess == null) {
            // try to find session if not specified
            sess = (HttpSession)Sessions.getCurrent().getNativeSession();
        }
        synchronized (sess) {
            // try to find Map for registered desktops
            registeredDesktops = (Map<String, Desktop>)sess.getAttribute(REGISTERED_DESKTOPS);
            if (registeredDesktops == null) {
                // create Map for registered desktops and store it
                // into session if not exists
                registeredDesktops = new Hashtable<String, Desktop>();
                sess.setAttribute(REGISTERED_DESKTOPS, registeredDesktops);
            }
        }
        return registeredDesktops;
    }
    /** Build AU response of a desktop
     * 
     * @param desktop the desktop to build AU response
     * @return String response content
     */
    public static String buildResponse (Desktop desktop) {
        Map<String, Map<String, String>> compMap = getAttributesToUpdate(desktop);
        // response JSON object
        JSONObject resp = new JSONObject();
        // for each component data
        for (Entry<String, Map<String, String>> entry : compMap.entrySet()) {
            Map<String, String> propMap = entry.getValue();
            // prop/value JSON object
            JSONObject propAndVal = new JSONObject();
            // for each prop/value pair
            for (Entry<String, String> propEntry : propMap.entrySet()) {
                propAndVal.put(propEntry.getKey(), propEntry.getValue());
            }
            resp.put(entry.getKey(), propAndVal);
        }
        // clear data
        getAttributesToUpdate(desktop).clear();
        return resp.toJSONString();
    }
    /** Get Map that contains properties to update of a component
     * 
     * @param desktop specific desktop
     * @param id component id
     * @return
     */
    private static Map<String, String> getPropMap (Desktop desktop, String id) {
        // try to get propMap
        Map<String, Map<String, String>> compMap = getAttributesToUpdate(desktop);
        Map<String, String> propMap;
        propMap = compMap.get(id);
        if (propMap == null) {
            // create propMap if not exists
            propMap = new Hashtable<String, String>();
            compMap.put(id, propMap);
        }
        return propMap;
    }

    /** Get Map that contains components Map to update of a desktop
     * 
     * structure: componentMap<key, propertyValueMap<prop, value>>
     * 
     * @param desktop
     * @return
     */
    @SuppressWarnings("unchecked")
    private static Map<String, Map<String, String>> getAttributesToUpdate (Desktop desktop) {
        // try to get map
        Map<String, Map<String, String>> attributesToUpdate = 
            (Map<String, Map<String, String>>)desktop.getAttribute(ATTRIBUTES_TO_UPDATE);
        if (attributesToUpdate == null) {
            // create if not exists
            attributesToUpdate = new Hashtable<String, Map<String, String>>();
            desktop.setAttribute(ATTRIBUTES_TO_UPDATE, attributesToUpdate);
        }
        return attributesToUpdate;
    }
}


TestWebSocketServlet.java

Servlet for WebSocket, process request (msg) response in it.

package impl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.catalina.websocket.MessageInbound;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;
import org.zkoss.json.JSONArray;
import org.zkoss.json.JSONObject;
import org.zkoss.json.JSONValue;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;

import components.IWebSocketEnhancedComponent;

/**
 * Tested with Tomcat 7.0.42 and ZK 6.5.2
 * @author benbai123
 *
 */
public class TestWebSocketServlet extends WebSocketServlet {

    private static final long serialVersionUID = -7663708549630020769L;

    /**
     * For create connection only, each connection will
     * handle it self as needed
     */
    @Override
    protected StreamInbound createWebSocketInbound(String subProtocol,
            HttpServletRequest request) {
        // request uri, format is desktopId.wsreq
        String uri = request.getRequestURI();

        // get desktopId from uri
        String desktopId = uri.substring(uri.lastIndexOf("/")+1, uri.length()).replace(".wsreq", "");

        // create MessageInbound with desktop ID and session
        return new TestMessageInbound(desktopId, request.getSession());
    }
    private final class TestMessageInbound extends MessageInbound {
        // hold desktop id and session
        private String _desktopId;
        private HttpSession _session;
        // constructor
        public TestMessageInbound (String desktopId, HttpSession session) {
            _desktopId = desktopId;
            _session = session;
        }
        @Override
        protected void onOpen(WsOutbound outbound) {
            /* ignore */
        }
        // remove registered desktop
        @Override
        protected void onClose(int status) {
            DesktopUtils.removeRegisteredDesktop(_session, _desktopId);
        }
        // ignore binary message
        @Override
        protected void onBinaryMessage(ByteBuffer message) throws IOException {
            /* ignore */
        }
        /** Entry point, you can think it is similar to
         * service method (doGet/doPost) in common Servlet
         * 
         */
        @SuppressWarnings("unchecked")
        @Override
        protected void onTextMessage(CharBuffer message) throws IOException {
            // pass request to components
            // request from client: {
            //             dtid: DESKTOP_ID,
            //            [
            //                {"uuid": COMPONENT_ID_1, "evtnm": EVENT_NAME_1, "data": DATA_1},
            //                {"uuid": COMPONENT_ID_2, "evtnm": EVENT_NAME_2, "data": DATA_2},
            //                ...
            //            ]
            //        }

            // parse to JSON object
            JSONObject jsObj = (JSONObject)JSONValue.parse(message.toString());
//            System.out.println("desktop: " + jsObj.get("dtid"));
            // enable the line below to see (merged) request from Client
//            System.out.println("requests: " + jsObj.get("requests"));

            // get desktop
            Desktop desktop = DesktopUtils.getRegisteredDesktop(_session, (String)jsObj.get("dtid"));
            JSONArray requests = (JSONArray)JSONValue.parse((String)jsObj.get("requests"));
            // for each request in requests
            for (int i = 0; i < requests.size(); i++) {
                // get request
                JSONObject reqObj = (JSONObject)requests.get(i);
                // find component
                Component target = desktop.getComponentByUuidIfAny((String)reqObj.get("uuid"));
                // get data
                Map<Object, Object> data = (Map<Object, Object>)reqObj.get("data");
                // create request object
                RequestFromWebSocket req = new RequestFromWebSocket((String)reqObj.get("evtnm"), target, data);
                // pass request object to service method of component
                ((IWebSocketEnhancedComponent)target).serviceWebSocket(req);
            }
            // build and send response after service
            sendResponse(this, desktop);
        }
    }
    /** Send message via WebSocket to specific desktop
     * 
     * @param connection connection to use
     * @param desktop used to build response
     */
    public static void sendResponse (TestMessageInbound connection, Desktop desktop) {
        // build response
        String response = DesktopUtils.buildResponse(desktop);
        try {
            // send response
            connection.getWsOutbound().writeTextMessage(CharBuffer.wrap(response));
        } catch (IOException ignore) {
            /* ignore */
        }
    }
}


zk.xml

Override javascript functions to support WebSocket.

<zk>
    <device-config>
        <device-type>ajax</device-type>
        <embed><![CDATA[
            <script type="text/javascript">
                zk.afterLoad("zul", function () {
                    var _wgt = {};

                    zk.override(zk.Widget.prototype, _wgt, {
                        // setter for set context of WebSocket
                        setUseWebSocketAU: function (v) {
                            if (v != this._useWebSocketAU)
                                this._useWebSocketAU = v;
                        },
                        bind_: function (dt, skipper, after) {
                            // call original function
                            _wgt.bind_.apply(this, arguments);
                            // initiate WebSocket after bind_
                            if (this._useWebSocketAU)
                                this.helpDesktopToInitWebSocket();
                        },
                        // init WebSocket
                        helpDesktopToInitWebSocket: function () {
                            var desktop = this.desktop;
                            // if didn't init
                            if (desktop && !desktop.TestWebSocket) {
                                // override desktop to support WebSocket
                                overrideDesktop(desktop);
                                desktop.TestWebSocket.connect();
                            }
                        }
                    });
                });
                // Override Timer widget and desktop since
                // we do not want to override zAu / jq.xhr for this POC
                zk.afterLoad("zul.utl", function () {
                    var _tmWgt = {};

                    zk.override(zul.utl.Timer.prototype, _tmWgt, {
                        // onTimer
                        _tmfn: function () {
                            if (!this._repeats) this._running = false;
                            // whether _useWebSocketAU?
                            if (this._useWebSocketAU
                                && this.desktop.webSocketReady) {
                                // build and send request via WebSocket
                                var req = {uuid: this.uuid,
                                            evtnm: 'onTimer',
                                            data: {}
                                        };
                                this.desktop.sendRequestToWebSocket(req);
                            } else // call original function
                                this.fire('onTimer', null, {ignorable: true});
                        }
                    });
                });
                function overrideDesktop (desktop) {
                    desktop.TestWebSocket = {
                        socket: null,
                        connect: (function() {
                            // .wsreq for servlet mapping defined in web.xml
                            var path = window.location.host + window.location.pathname,
                                host = 'ws://' + path + desktop.id + '.wsreq';
                            if ('WebSocket' in window) {
                                this.socket = new WebSocket(host);
                            } else if ('MozWebSocket' in window) {
                                this.socket = new MozWebSocket(host);
                            } else {
                                alert('Error: WebSocket is not supported by this browser.');
                                return;
                            }
                            // process message from server
                            this.socket.onmessage = function (msg) {
                                desktop.doWebSocketMessage_(msg);
                            };
                            // store ready state for components to check
                            this.socket.onopen = function () {
                                desktop.webSocketReady = true;
                            };
                        }),
                        disconnect: function () {
                            // close and clear
                            this.socket.close();
                            this.socket = null;
                            desktop.TestWebSocket = null;
                        },
                        // send message to server
                        sendRequestToWebSocket: (function(msg) {
                            this.socket.send(msg);
                        })
                    };
                    desktop.doWebSocketMessage_ = function (msg) {
                        // parse response (msg.data)
                        // pattern: {
                        //            componentId: {prop: val, prop2: val2, ...},
                        //            componentId2: {prop: val, prop2: val2, ...}, ...
                        //        }
                        // Enable the line below to see (merged) response from server
                        // zk.log(msg.data);
                        var resp = jq.evalJSON(msg.data),
                            props, // properties ({key: value, ...}) to update
                            val,
                            setter,
                            wgt;
                        // for each component (ID)
                        for (var key in resp) {
                            // get widget by ID
                            wgt = zk.Widget.$('#'+key);
                            // get properties to update by ID
                            props = resp[key];
                            // for each property
                            for (var prop in props) {
                                // get value by property name
                                val = props[prop];
                                // build setter by property name (xyz -> setXyz)
                                setter = 'set' + prop.charAt(0).toUpperCase() + prop.slice(1);
                                // call setter to set value to widget
                                wgt[setter](val);
                            }
                        }
                    };
                    // API for widget to send request to server
                    desktop.sendRequestToWebSocket = function (req) {
                        if (!desktop.eventArrayForWebSocket)
                            desktop.eventArrayForWebSocket = [];
                        // push req into an array
                        desktop.eventArrayForWebSocket.push(req);
                        // send multiple req with single request
                        if (!desktop.sendRequestTimerForWebSocket) {
                            desktop.sendRequestTimerForWebSocket = setTimeout(function () {
                                var jsonToSend = jq.toJSON({
                                    dtid: desktop.id,
                                    requests: jq.toJSON(desktop.eventArrayForWebSocket)
                                });
                                desktop.TestWebSocket.sendRequestToWebSocket(jsonToSend);
                                desktop.eventArrayForWebSocket = desktop.sendRequestTimerForWebSocket = null;
                            }, 50);
                        }
                    };
                }
            </script>
        ]]></embed>
    </device-config>
</zk>


web.xml

Define servlet for WebSocket.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <display-name>ReplaceAuRequestWithWebSocket</display-name>
    <servlet>
        <servlet-name>testWebSocketServlet</servlet-name>
        <servlet-class>impl.TestWebSocketServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>testWebSocketServlet</servlet-name>
        <url-pattern>*.wsreq</url-pattern>
    </servlet-mapping>
</web-app>


Reference

ZK Source
http://github.com/zkoss/zk/tree/master/zul/src/

Download

Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Integrate/WebSocket/AURequestWithWebSocket

Demo Flash
https://github.com/benbai123/ZK_Practice/blob/master/demo_src/swf/Integrate/WebSocket/AURequestWithWebSocket.swf

Saturday, March 3, 2012

Basic Servlet Practice: Dynamic add servlet programmatically.

Sometimes we may want to dynamically add the servlet and/or change their feature,
for example, support the servlet 3 asynchronous processing.

To achieve this, we can dynamically add servlet in
ServletContextListener as below:


DynamicAddListener.java

package test.servlet.dynamicaddservlet;

import javax.servlet.*;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.WebListener;

/**
 * This listener will add the DynamicAddedServlet if servlet version is 3.0 or later
 *
 */
@WebListener()
public class DynamicAddListener implements ServletContextListener {
    private static final long serialVersionUID = -8873939883201271898L;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        // Dynamic add servlet for servlet 3.x or later
        ServletContext sc = event.getServletContext();
        if (sc.getMajorVersion() >= 3) {
            final ServletRegistration.Dynamic dn =
                    sc.addServlet("DynamicAddedServlet", DynamicAddedServlet.class);
            dn.setAsyncSupported(true);
            dn.addMapping("/added.jsp");
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        
    }
}

DynamicAddedServlet.java

package test.servlet.dynamicaddservlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This servlet will be added by DynamicAddListener#contextInitialized
 *
 */
public class DynamicAddedServlet extends HttpServlet {

    private static final long serialVersionUID = -8873939883201271898L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        ServletContext sc = getServletContext();
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title> Servlet Added </title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h2 style=\"margin: 50px;\">The Servlet Version is 3.0 or later so<br />"
                    + "&nbsp;the Servlet is added.<br /><br />"
                    + "&nbsp;&nbsp;Servlet Version: "+sc.getMajorVersion()+"."+sc.getMinorVersion()+"</h2>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    }
}


If we run this Web-App with Tomcat7 then connect to /added.jsp,
the result is:



Download:

Files at github:
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/BasicServletPractice/src/test/servlet/dynamicaddservlet

Reference:

http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html#addServlet(java.lang.String, java.lang.Class)

Tuesday, February 28, 2012

RPC Practice: Practice of Redstone RPC Server and RPC Client

XML-RPC is a remote procedure call (RPC) protocol which
uses XML to encode its calls and HTTP as a transport mechanism.

Redstone XML-RPC Library is a implementation of the XML-RPC specification.

This post practice create the RPC server and
call service by RPC client with Redstone XML-RPC Library.

1. The RPC Server
1.1 The XmlRpcServlet and related classes

TestService.java

package test;

import javax.servlet.*;

import redstone.xmlrpc.XmlRpcServlet;

/**
 * The servlet that extends redstone.xmlrpc.XmlRpcServlet
 */
public class TestService extends XmlRpcServlet {
    /**
     * auto-generated serial version UID
     */
    private static final long serialVersionUID = 7764199825847356985L;

    public void init( ServletConfig servletConfig )
        throws ServletException {
        // init
        super.init( servletConfig );
        // regist the Service
        // HelloClockService is the service name for client to invoke
        // new HelloClock() is the instance that provide this service
        getXmlRpcServer().addInvocationHandler( "HelloClockService", new HelloClock());
    }
}

only one handler in this servlet named "HelloClockService" handled by
an instance of HelloClock

HelloClock.java

package test;

/** this class provide the HelloClockService,
  * only one service 'sayHello'
  */
public class HelloClock {
    /**
     * sayHello service of HelloClockService
     * @param name the name to say hello
     * @return HelloClockMessage contains hello message and current time
     */
    public HelloClockMessage sayHello (String name) {
        return new HelloClockMessage(name);
    }
}

HelloClock provide one service, sayHello, with one String parameter.
It will return an instance of HelloClockMessage

HelloClockMessage.java


package test;

import java.util.*;

/**
 * The message that will reply to client by
 * HelloClockService.sayHello
 */
public class HelloClockMessage {
    private String _msg;
    private Date _date;
    public HelloClockMessage () {
        
    }
    public HelloClockMessage (String name) {
        _msg = "Hello " + name;
        _date = new Date();
    }
    public HelloClockMessage (String msg, Date date) {
        _msg = msg;
        _date = date;
    }
    public String getMsg () {
        return _msg;
    }
    public Date getDate () {
        return _date;
    }
}

HelloClockMessage create the msg by the input name,
and create a Date object

The return message of HelloClockService will contains two parameter,
msg and date, with respect to the getter getMsg () and getDate () in HelloClockMessage

1.2 The servlet config (in web.xml)

config for xml-rpc

<servlet>
        <servlet-name>xml-rpc</servlet-name>
        <servlet-class>test.TestService</servlet-class>
        <init-param>
            <param-name>streamMessages</param-name>
            <param-value>1</param-value>
        </init-param>
        <init-param> <!-- Optional! Defaults to text/xml and ISO-8859-1 -->
            <param-name>contentType</param-name>
            <param-value>text/xml; charset=UTF-8</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>xml-rpc</servlet-name>
        <url-pattern>/xml-rpc/*</url-pattern>
    </servlet-mapping>

2. The RPC Client

TestRPCClient.java

package test;

import java.util.*;

import redstone.xmlrpc.XmlRpcCallback;
import redstone.xmlrpc.XmlRpcClient;
import redstone.xmlrpc.XmlRpcException;
import redstone.xmlrpc.XmlRpcStruct;
/**
 * test RPC call
 */
public class TestRPCClient {
    // the callback for async RPC
    private static XmlRpcCallback xrc = new XmlRpcCallback() {
        public void onException(XmlRpcException exception) {
            exception.printStackTrace();
        }
        public void onFault(int faultCode, java.lang.String faultMessage) {}
        public void onResult(java.lang.Object result) {
            System.out.println("########## Async RPC Callback ##########");
            // get the HelloClockMessage object by the response
            HelloClockMessage hcm = XmlToHelloClockMessage((XmlRpcStruct)result);

            // Show the value of created HelloClockMessage
            System.out.println(hcm.getMsg());
            System.out.println(hcm.getDate().getHours() + ":" + hcm.getDate().getMinutes());
        }
    };
    /**
     * convert XmlRpcStruct to HelloClockMessage
     * @param resp the XmlRpcStruct response
     * @return HelloClockMessage object
     */
    public static HelloClockMessage XmlToHelloClockMessage (XmlRpcStruct resp) {
        HelloClockMessage hcm =
            new HelloClockMessage((String)resp.get("msg"), (Date)resp.get("date"));
        return hcm;
    }
    public static void main( String[] args ) throws Exception {
        // create client
        XmlRpcClient client =
            new XmlRpcClient( "http://localhost:8080/RPCServer/xml-rpc", true);
        // invoke RPC method with param "Ben"
        // get the response
        XmlRpcStruct resp =
            (XmlRpcStruct)client
                .invoke( "HelloClockService.sayHello", new Object[] {"Ben"});
        // get the HelloClockMessage object by the response
        HelloClockMessage hcm = XmlToHelloClockMessage(resp);

        // show the Class of response parameter
        System.out.println(resp.get("date").getClass());
        System.out.println(resp.get("msg").getClass());

        // Show the value of created HelloClockMessage
        System.out.println(hcm.getMsg());
        System.out.println(hcm.getDate().getHours() + ":" + hcm.getDate().getMinutes());

        System.out.println();

        // invoke RPC Asynchronously
        client.invokeAsynchronously("HelloClockService.sayHello",
                new Object[] {"Benbai"}, xrc);
    }
}

This client call RPC twice,
one sync call, one async call with a callback object.

The HelloClockMessage is the same with server's one.

3. Test result

Run TestRPCClient as Java Program:


Download:

The full project is at github
RPCClient:
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/WebServicePractice/RPC/RPCClient
RPCServer:
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/WebServicePractice/RPC/RPCServer

References:

Redstone:
http://xmlrpc.sourceforge.net/

wiki:
http://en.wikipedia.org/wiki/XML-RPC

Wednesday, January 25, 2012

Basic Servlet Practice

A project of really basic practice, not good practice, just practice.

You can download the basic practice project from github
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/BasicServletPractice

Description:

index.html

This page practice two things:
Use AJAX to get the hint of id and password,
it send the http get request to context 'auth.hint',

Use form submit to do login,
the submit form action is the http post to context 'login.go',

error.jsp

This is the error page defined in 'web.xml',
it simply display the exception message

the exception is from login servlet

content.html

This page practice JSON,
it use AJAX to get the content from ContentServlet,
and use eval method to transfer the content to array,
the array combined by a string and two inner array,
the first string is a javascript command which executed by eval method,
the two inner array are the content and displayed by that command.

If it receive the bad request status,
it will redirect to login page

Hint.java

This file practice set/get attribute from session,
output the text to client and config servlet by annotation.

It provide the hint of id and password,
or show alert message if ask too much times.

Login.java

This file practice get parameter from request,
redirect to another page.
It config in web.xml

It get the input id and password,
if it is correct, redirect to content page,
throw ServletException with message 'Login fail' otherwise

Content.java

This file practice JSON,
it also config by annotation.

It create two small JSON array,
then put a command that will execute at client and
the two small array into a big one.

** To use JSON, the jars below are required
json-lib-2.4-jdk15.jar, ezmorph-1.0.6.jar, commons-logging-1.1.1.jar,
commons-lang-2.5.jar, commons-collections-3.2.1.jar, commons-beanutils-1.8.3.jar

Ref: http://json-lib.sourceforge.net/

Logout.java

This file invalidate the session then redirect to login page.

ReqFilter.java

This file practice the filter, and config filter by annotation

This is a filter protect the content page,
it triggered by the 'content.gen' context
(the content page send request to it to get content)

if a session do not contain the 'Login' data,
the request can not pass this filter and will get the 'bad request' status.