Sunday, October 27, 2013

Compile LESS with Java


Simple Note

From official site:
For production and especially if performance is important, we recommend pre-compiling using node or one of the many third party tools.

This article describe how to compile .less file to .css file in java code on server start-up.

Pre-request

LESS Getting Started
http://ben-bai.blogspot.tw/2013/10/less-getting-started.html

Do Something on Server Startup/Shutdown
http://ben-bai.blogspot.tw/2013/10/do-something-on-server-startupshutdown.html

Result

The same as the result in previous post.

Required jars

lesscss-engine-1.3.3.jar
http://mvnrepository.com/artifact/com.asual.lesscss/lesscss-engine/1.3.3

rhino-1.7R4.jar
http://mvnrepository.com/artifact/org.mozilla/rhino/1.7R4

commons-logging-1.1.1.jar
http://mvnrepository.com/artifact/commons-logging/commons-logging/1.1.1

Code

test.less

Copied from previous post, nothing changed.

https://github.com/benbai123/JSP_Servlet_Practice/blob/master/Practice/LESS/CompileLESSWithJava/WebContent/resources/less/test.less

less_test.html

Modified from previous post, load compiled css file instead of less file and do not load less.js.

https://github.com/benbai123/JSP_Servlet_Practice/blob/master/Practice/LESS/CompileLESSWithJava/WebContent/less_test.html

TestServletContextListener.java

Implements ServletContextListener, will compile less file to css file on server start-up.

package test;

import java.io.File;
import java.io.IOException;

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

import com.asual.lesscss.LessException;

import test.util.LESSUtils;

public class TestServletContextListener implements ServletContextListener {
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // do nothing on server shutdown
    }
    // compile less on server startup
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            String webContentPath = sce.getServletContext().getRealPath("/");
            LESSUtils.compile(new File(webContentPath + File.separator + "resources" + File.separator + "less" + File.separator + "test.less"),
                    new File(webContentPath + File.separator + "resources" + File.separator + "css" + File.separator + "test.css"));
        } catch (LessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}


LESSUtils.java

Contains utility function to compile less file, say Utils means (probably) will add more functions in future work.

package test.util;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import com.asual.lesscss.LessEngine;
import com.asual.lesscss.LessException;

public class LESSUtils {
    public static void compile (File lessFile, File cssFile) throws LessException, IOException {
        if (lessFile == null 
                || !lessFile.exists()) {
            // less file should exist
            throw new FileNotFoundException("LESS file not exists !");
        }
        // create less engine
        LessEngine engine = new LessEngine();
        if (!cssFile.getParentFile().exists()) {
            // create folders for css file
            boolean cssFileCreated = cssFile.getParentFile().mkdirs();
            if (!cssFileCreated) {
                throw new IOException("Folders for CSS file are not created !");
            }
        }
        // compile less file to css file
        engine.compile(lessFile, cssFile);
    }
}


web.xml

Define listener.

https://github.com/benbai123/JSP_Servlet_Practice/blob/master/Practice/LESS/CompileLESSWithJava/WebContent/WEB-INF/web.xml

References

Java Compiler for Less CSS?
http://stackoverflow.com/questions/9739724/java-compiler-for-less-css

LESS Engine
https://github.com/asual/lesscss-engine

Download

Full project at github
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/LESS/CompileLESSWithJava

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

Sunday, October 20, 2013

LESS Recursive Mixin


Simple Note

Pre-request

LESS Getting Started
http://ben-bai.blogspot.tw/2013/10/less-getting-started.html

Code

It is funny to write stylesheet with recursive function but probably not a good practice.

index.html

Simple html page
https://github.com/benbai123/JSP_Servlet_Practice/blob/master/Practice/LESS/RecursiveMixin/WebContent/index.html

test.less

Define a mixin .box to create nested selectors recursively.

// comments to displayed in compiled css
/* compiled css */

// define variable
@base-color: #EEEEEE;
@dec-base: #111111;

// define Mixin behave like function
// parameter @idx with default value 1
.box(@idx: 1) when (@idx < 6) {
    // create selector
    .class@{idx} {
        // border with calculated border color,
        // e.g., #EEEEEE - #111111*1*2 = #CCCCCC
        border: 10px solid (@base-color - @dec-base*@idx*2);
        display: inline-block;
        // increase @idx,
        // call .box recursively to create nested selectors
        .box(@idx + 1);
    } 
}
// call .box with default @idx
.box();


Result

Click to view large image



References

Official site
http://lesscss.org/

Do a loop with LESS css
http://blog.thehippo.de/2012/04/programming/do-a-loop-with-less-css/

Download

Full project at github
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/LESS/RecursiveMixin



Saturday, October 19, 2013

LESS Getting Started


Simple Note

Code

Actually this is a pure html case, the only reason that I put it in a Dynamic Web Project is that I want to use the exiting run-jetty-run plugin in my Eclipse since I am lazy to config a server for it.

less_test.html

Simple html page, declare less file and load less.js. Say 'declare' since the .less file seems loaded by less.js with AJAX instead of load by link directly.

<html>
    <head>
        <!--  include .less file, need to include it before less.js -->
        <link rel="stylesheet/less" type="text/css" href="resources/test.less" />
        <!-- include less.js, will compile .less file at client side -->
        <script src="resources/less-1.4.2.min.js" type="text/javascript"></script>
    </head>
    <body>
        <!-- nested div -->
        <div class="class1">
            <div class="class2">
                <div class="class3">
                    <div class="class4">
                        <div class="class5"></div>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>


test.less

Stylesheet written by LESS.

// use // to comment in less file only
// use /* */ to commment in less and compiled css
/* compiled css */
// define variable
@base-color: #EEEEEE;

// define Mixin behave like function
// parameter @dec with default value #111111
.box(@dec: #111111) {
    // border with calculated border color,
    // e.g., #EEEEEE - #111111*2 = #CCCCCC
    border: 10px solid (@base-color - @dec*2);
    display: inline-block;
}

// nest selectors inside other selectors
.class1 {
    .box; // include .box with default value of parameter
    .class2 { // .class1 .class2
        .box(#222222); // include .box with @dec = #222222
        .class3 { // .class1 .class2 .class3
            .box(#333333);
            .class4 { // .class1 .class2 .class3 .class4
                .box(#444444);
                .class5 { // .class1 .class2 .class3 .class4 .class5
                    .box(#555555);
                }
            }
        }
    }
}


And less.js downloaded from official site of LESS.

Result

Click to view large image



Reference

LESS
http://lesscss.org/

Download

Full project at github
https://github.com/benbai123/JSP_Servlet_Practice/tree/master/Practice/LESS/GettingStarted

Sunday, October 6, 2013

WebSocketServerPush vs ZKComet Performance Testing


Simple Note

Testing Environment

Server:

Tomcat 7.0.42

ZK Version:

6.5.2

WebSocket ServerPush Implementation:

ZK ServerPush with WebSocket - Rework
http://ben-bai.blogspot.tw/2013/10/zk-serverpush-with-websocket-rework.html

Testing Steps

1. Start Tomcat

2. Open visualVM

3. Open 5 testing pages

There are 2 cases for WebSocket ServerPush, push to desktop separately (index.zul) and push to all desktop at once (index_per_desktop.zul), 1 case for ZK Comet (push to desktop separately - index.zul)

4. Wait 2 minutes

5. Take snapshot

Testing Result

(Click snapshot to view large image)

Executing Result:

ZK Comet - index.zul



WebSocket ServerPush - index.zul



WebSocket ServerPush - index_per_desktop.zul



JVisualVM

ZK Comet - index.zul



WebSocket ServerPush - index.zul



WebSocket ServerPush - index_per_desktop.zul



Observation

Execution Speed

WebSocket ServerPush is two times faster than ZK Comet

Memory Consumption

Highest: WebSocket ServerPush (215/187 MB for per-desktop/push-to-all) is two times higher than ZK Comet (100MB), resonable since WebSocket is faster, do more work and use more memory at the same time.

Lowest: Almost the same (20 ~ 23 MB), most of the memory can be GCed.

CPU Usage

WebSocket AU (2%~8%/1%~6% for per-desktop/push-to-all after page loaded) is better than ZK Comet (1%~24% after page loaded).

Conclution

WebSocket do double work with 1/4 ~ 1/3 CPU Usage and double Memory Consumption.

Download

Testing projects at github
https://github.com/benbai123/ZK_Practice/tree/master/General/Performance_Testing/WebSocketServerPush_vs_ZKComet

Saturday, October 5, 2013

ZK ServerPush with WebSocket - Rework


Introduction

This article describe how to use WebSocket to do ServerPush in ZK MVVM, modified from previous article ZK: Server Push with WebSocket (http://ben-bai.blogspot.tw/2013/07/zk-server-push-with-websocket.html) with the desktop based implementation from ZK AURequest with WebSocket (http://ben-bai.blogspot.tw/2013/09/zk-aurequest-with-websocket.html).

It integrates WebSocket in ZK for ServerPush in more general way and has better performance (compared with previous component based version).

Environment: Tomcat 7.0.42, ZK 6.5.2

NOTE: This is just a POC with some customized components.

Pre-request

ZK AURequest with WebSocket
http://ben-bai.blogspot.tw/2013/09/zk-aurequest-with-websocket.html

ZK Create Helper Tag to Make Programmer Happier
http://ben-bai.blogspot.tw/2013/09/zkcreatehelpertag-to.html

ZK Basic MVVM Pattern
http://ben-bai.blogspot.tw/2012/12/zk-basic-mvvm-pattern.html

Result

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

Program

There are lots of source files, will only post code for some of them and post link for others.

index.zul

Entry page

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/WebContent/index.zul

push_to_all.zul

Push value to all desktop (i.e., Application scope) with button click.

<zk>
    <!-- Tested with ZK 6.5.2 -->
    <window apply="org.zkoss.bind.BindComposer"
        viewModel="@id('vm') @init('test.TestPushToAllVM')">
        Counter:
        <!-- intbox, listen to positive integer in the begining -->
        <intbox readonly="true" />
            <contextBinding field="value" context="@load(vm.counter)" />
        listen to: <label value="@load(vm.counter)" />
        <div />
        inverse Counter:
        <!-- intbox, listen to negative integer in the begining -->
        <intbox id="inverseCounter" readonly="true" />
            <contextBinding field="value" context="@load(vm.inverseCounter)" />
        listen to: <label value="@load(vm.inverseCounter)" />
        <div />
        <!-- update value of positive/negative integer -->
        <button id="updateCounterBtn" label="updaet counter and negative counter"
            onClick="@command('updateCounter')" />
        <!-- switch listening context of the two intboxess above -->
        <button label="switch context" id="switchBtn"
            onClick="@command('switchCounter')" />
    </window>
</zk>


test.TestPushToAllVM.java

VM for push_to_all.zul

package test;

import impl.serverpush.ServerPushUtil;

import java.util.concurrent.atomic.AtomicInteger;

import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;

/** Tested with ZK 6.5.2
 * 
 * @author benbai123
 *
 */
public class TestPushToAllVM {
    /** context listened by intboxes */
    private String _counter = "positive";
    private String _inverseCounter = "negative";
    /** desktop of this VM */
    private Desktop targetDesktop;

    /** counter used to update positive/negative integer */
    private static AtomicInteger _cntCounter = new AtomicInteger(0);

    // getters
    public String getCounter () {
        return _counter;
    }
    public String getInverseCounter () {
        return _inverseCounter;
    }
    /**
     * update value to context 'positive' and 'negative' via
     * WebSocket
     * 
     * All components that listen to these context will be updated
     */
    @Command
    public void updateCounter () {
        int val = _cntCounter.incrementAndGet();
        push(val, "positive", false);
        push(-1*val, "negative", false);
    }
    /** switch listening components of 'positive' and 'negative' context
     * current desktop only
     * 
     */
    @Command
    @NotifyChange({"counter", "inverseCounter"})
    public void switchCounter () {
        int val = _cntCounter.get();
        // push to opposite (for self desktop) before switch context
        push(val, "negative", true);
        push(-1*val, "positive", true);
        String tmp = _counter;
        _counter = _inverseCounter;
        _inverseCounter = tmp;
        
    }

    /** push value
     * 
     * @param val
     * @param context
     */
    private void push (Object val, String context, boolean desktopOnly) {
        try {
            if (targetDesktop == null) {
                targetDesktop = Executions.getCurrent().getDesktop();
            }
            if (desktopOnly) {
                // push to current desktop only
                ServerPushUtil.pushVlaue(val, context, targetDesktop);
            } else {
                // push to all desktop
                ServerPushUtil.pushVlaue(val, context);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


automatic_push.zul

Update value with java thread, Desktop scope.

<zk>
    <!-- Tested with ZK 6.5.2 -->
    <window apply="org.zkoss.bind.BindComposer"
        viewModel="@id('vm') @init('test.TestAutoPushVM')">
        Self:
        <!-- intbox that will be updated -->
        <intbox readonly="true" />
            <contextBinding field="value" context="@load(vm.task)" />
        <!-- start update -->
        <button label="start" onClick="@command('start')" />
        <!-- stop update -->
        <button label="stop" onClick="@command('stop')" />
        <!-- textbox that bind rows/cols to integer -->
        <textbox />
            <contextBinding id="cbd" field="@load(vm.prop)" context="@load(vm.task)" />
        <button label="switch field (rows/cols)" onClick="@command('switchRowCol')" />
    </window>
</zk>


test.TestAutoPushVM.java

VM for automatic_push.zul

package test;

import impl.serverpush.ServerPushUtil;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;

/** Tested with ZK 6.5.2
 * 
 * @author benbai123
 *
 */
public class TestAutoPushVM {

    /** Counter used to update integer */
    private AtomicInteger _cnt = new AtomicInteger(0);
    /** ServerPush task timer */
    private Timer timer;
    /** Desktop of this VM */
    private Desktop targetDesktop;
    /** Binded field of textbox */
    private String _prop = "rows";

    // getters
    public String getTask () {
        return "timerTask";
    }
    public String getProp () {
        return _prop;
    }
    /**
     * start server push with WebSocket for
     * specific context "timerTask"
     * 
     * current desktop only
     */
    @Command
    public void start () {
        if (timer == null) {
            // update once immediately when first time start
            if (_cnt.get() == 0) {
                push(_cnt.incrementAndGet(), "timerTask");
            }
            timer = new Timer();
            timer.schedule(getTimerTask(), 1000, 1000);
        }
    }
    /**
     * stop server push with WebSocket for
     * specific context "timerTask"
     * 
     * current desktop only
     */
    @Command
    public void stop () {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }
    @Command
    @NotifyChange("prop")
    public void switchRowCol () {
        if ("rows".equals(_prop)) {
            _prop = "cols";
        } else {
            _prop = "rows";
        }
    }
    // task to be scheduled to update context "timerTask" every second
    private TimerTask getTimerTask () {
        return new TimerTask() {
            public void run () {
                push(_cnt.incrementAndGet(), "timerTask");
            }
        };
    }
    /** push value to specific context
     * 
     * @param val value to push
     * @param context context to push
     */
    private void push (Object val, String context) {
        try {
            if (targetDesktop == null) {
                targetDesktop = Executions.getCurrent().getDesktop();
            }
            // push to current desktop only
            ServerPushUtil.pushVlaue(val, context, targetDesktop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


components.IWebSocketEnhancedComponent.java

Copied from AURequestWithWebSocket, completely the same.

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/components/IWebSocketEnhancedComponent.java

components.Intbox.java

Copied from AURequestWithWebSocket, change _useWebSocketAU default to true and override smartUpdate instead of setter.

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/components/Intbox.java

components.Textbox.java

Created for this POC, override smartUpdate instead of override setters separately.
Almost the same with Intbox (actually completely the same after serialVersionUID

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/components/Textbox.java

components.serverpush.IWebSocketServerPushEnhancedComponent.java

Define a "WebSocket Enhanced Component -- ServerPush type" for ServerPush pattern

package components.serverpush;

import impl.serverpush.Binding;
import components.IWebSocketEnhancedComponent;

/** Tested with ZK 6.5.2<br>
 * 
 * Define a "WebSocket Enhanced Component -- ServerPush type"
 * for ServerPush pattern
 * 
 * @author benbai123
 *
 */
public interface IWebSocketServerPushEnhancedComponent extends IWebSocketEnhancedComponent {
    /** Add Binding for specific field/context pair
     * 
     * The added Binding will be stored to a Binding List of a Component
     * 
     * @param field field to bind
     * @param context context to bind with field
     * @return the Added Binding
     */
    public Binding addSocketContextBinding (String field, String context);
    /** Remove Binding for specific field/context pair
     * 
     * Remove it from Binding List of a Component
     * 
     * @param field field of field/context pair to remove
     * @param context context of field/context pair to remove
     */
    public void removeSocketContextBinding (String field, String context);
}


components.serverpush.Intbox.java

Created for this POC, implements IWebSocketServerPushEnhancedComponent to support WebSocket ServerPush Almost the same with Textbox (completely the same after serialVersionUID)

package components.serverpush;

import impl.serverpush.Binding;
import impl.serverpush.ServerPushUtil;

/** Tested with ZK 6.5.2<br>
 * 
 * Created for this POC, implements IWebSocketServerPushEnhancedComponent to
 * support WebSocket ServerPush
 * 
 * Almost the same with Textbox (completely the same after serialVersionUID)
 * 
 * @author benbai123
 *
 */
public class Intbox extends components.Intbox implements IWebSocketServerPushEnhancedComponent {

    private static final long serialVersionUID = -3277498174057967067L;

    @Override
    public Binding addSocketContextBinding(String field, String context) {
        return ServerPushUtil.addContextBinding(this, field, context);
    }

    @Override
    public void removeSocketContextBinding(String field, String context) {
        ServerPushUtil.removeContextBinding(this, field, context);
    }

}


components.serverpush.Textbox.java

Created for this POC, implements IWebSocketServerPushEnhancedComponent to support WebSocket ServerPush Almost the same with Intbox (completely the same after serialVersionUID)

package components.serverpush;

import impl.serverpush.Binding;
import impl.serverpush.ServerPushUtil;

/** Tested with ZK 6.5.2<br>
 * 
 * Created for this POC, implements IWebSocketServerPushEnhancedComponent to
 * support WebSocket ServerPush
 * 
 * Almost the same with Intbox (completely the same after serialVersionUID)
 * 
 * @author benbai123
 *
 */
public class Textbox extends components.Textbox implements IWebSocketServerPushEnhancedComponent {

    private static final long serialVersionUID = 2354643632056197764L;

    @Override
    public Binding addSocketContextBinding(String field, String context) {
        return ServerPushUtil.addContextBinding(this, field, context);
    }

    @Override
    public void removeSocketContextBinding(String field, String context) {
        ServerPushUtil.removeContextBinding(this, field, context);
    }

}


components.helper.ContextBinding.java

Helper component to help a component to bind field with ServerPush context

package components.helper;

import impl.serverpush.Binding;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Div;

import components.serverpush.IWebSocketServerPushEnhancedComponent;

/** Tested with ZK 6.5.2
 * 
 * Helper component to help a component to bind field with ServerPush context
 * 
 * @author benbai123
 *
 */
public class ContextBinding extends Div {

    private static final long serialVersionUID = -7156149643515776677L;
    /** field to bind with context (required) */
    private String _field = "";
    /** context to bind with field (required) */
    private String _context = "";
    /** specified id of target component (optional) */
    private String _target;
    /** used to store current binding (relatively old) for changing field/context */
    private Binding _oldBinding = null;
    /** found target */
    private IWebSocketServerPushEnhancedComponent _foundTarget;

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public ContextBinding () {
        // update target url while created
        addEventListener(Events.ON_CREATE, new EventListener () {
            public void onEvent (Event event) {
                updateTargetBinding();
            }
        });
        // do not output any html
        setWidgetOverride ("redraw", "function (out) {}");
    }
    // setters
    public void setTarget (String target) {
        _target = target;
    }
    public void setField (String field) {
        if (field == null) {
            field = "";
        }
        if (!field.equals(_field)) {
            _field = field;
            updateTargetBinding();
        }
    }
    public void setContext (String context) {
        if (context == null) {
            context = "";
        }
        if (!context.equals(_context)) {
            _context = context;
            updateTargetBinding();
        }
    }

    /** Update Binding of target Component when field/context is changed
     * 
     */
    private void updateTargetBinding () {
        if (!_field.isEmpty() && !_context.isEmpty()) {
            _foundTarget = findTarget();
            if (_foundTarget != null) {
                // remove old binding if exists
                if (_oldBinding != null) {
                    if (_field.equals(_oldBinding.getField())
                            && _context.equals(_oldBinding.getContext())) {
                        // already binded, do nothing
                        return;
                    }
                    _foundTarget.removeSocketContextBinding(_oldBinding.getField(), _oldBinding.getContext());
                }
                // add new binding and store it to _oldBinding
                _oldBinding = _foundTarget.addSocketContextBinding(_field, _context);
            }
        }
    }
    /* package */ IWebSocketServerPushEnhancedComponent getFoundTarget () {
        return _foundTarget;
    }
    /** Try to find target to update
     * The order to try: <br>
     * 1. Try to find fellow under the same space owner with specified "target" attribute.<br>
     * 2. Try to find whether previous sibling is IWebSocketServerPushEnhancedComponent.<br>
     * 3. Try to find whether previous sibling is ContextBinding and already found a target.<br>
     * 4. Try to find whether parent is IWebSocketServerPushEnhancedComponent.<br>
     * 
     * This way it can work without target attribute in most cases.
     * @return IWebSocketServerPushEnhancedComponent if any
     */
    /* package */ IWebSocketServerPushEnhancedComponent findTarget () {
        Component comp;
        Component previous = getPreviousSibling();
        // Try to find fellow under the same space owner with specified "target" attribute.
        if (_target != null && !_target.isEmpty()) {
            comp = getSpaceOwner().getFellowIfAny(_target);
            if (comp != null
                && (comp instanceof IWebSocketServerPushEnhancedComponent)) {
                return (IWebSocketServerPushEnhancedComponent) comp;
            }
        }
        // Try to find whether previous sibling is IWebSocketServerPushEnhancedComponent.
        if (previous instanceof IWebSocketServerPushEnhancedComponent) {
            return (IWebSocketServerPushEnhancedComponent) previous;
        }
        // Try to find whether previous sibling is ContextBinding and already found a target.
        if (previous instanceof ContextBinding) {
            IWebSocketServerPushEnhancedComponent previousTarget = ((ContextBinding) previous).getFoundTarget();
            if (previousTarget != null) {
                return previousTarget;
            }
        }
        // Try to find whether parent is IWebSocketServerPushEnhancedComponent.
        if (getParent() instanceof IWebSocketServerPushEnhancedComponent) {
            return (IWebSocketServerPushEnhancedComponent)getParent();
        }
        return null;
    }
}


impl.DesktopUtils.java

Modified from AURequestWithWebSocket, almost the same, add synchronized for ServerPush.

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/DesktopUtils.java

impl.TestWebSocketServlet.java

Modified from AURequestWithWebSocket, almost the same, store channel at desktop so can send response with desktop itself.

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/TestWebSocketServlet.java

impl.EventListener.java

Define an Event listener used to process event from Not used in this POC.

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/EventListener.java

impl.RequestFromWebSocket.java

Copied from AURequestWithWebSocket, completely the same excepts this fragment. Not used in this POC

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/src/impl/RequestFromWebSocket.java

impl.serverpush.Binding.java

Binding that bind a field of a component to specific context for WebSocket ServerPush.

package impl.serverpush;

import java.io.Serializable;

/** Binding that bind a field of a component to specific context for WebSocket ServerPush
 * 
 * @author benbai123
 *
 */
public class Binding implements Serializable {
    private static final long serialVersionUID = -1591783705902661276L;
    /** field to bind of component */
    private String _field;
    /** context to bind with field */
    private String _context;
    // Constructor
    public Binding (String field, String context) {
        if (field == null) {
            field = "";
        }
        if (context == null) {
            context = "";
        }
        _field = field;
        _context = context;
    }
    // getters, setters
    public void setField (String field) {
        if (field == null) {
            field = "";
        }
        _field = field;
    }
    public String getField () {
        return _field;
    }
    public void setContext (String context) {
        if (context == null) {
            context = "";
        }
        _context = context;
    }
    public String getContext () {
        return _context;
    }
    // super
    public int hashCode () {
        return (_field + _context).hashCode() * 31;
    }
    public boolean equals (Object obj) {
        if (obj != null
                && (obj instanceof Binding)) {
            Binding b2 = (Binding) obj; 
            return (_field.equals(b2.getField())
                    && _context.equals(b2.getContext()));
        }
        return false;
    }
}


impl.serverpush.ServerPushUtil.java

Utilities for ServerPush with WebSocket, concept is similar to ServerPushWithWebSocket -- Component Oriented Version, rewritten it to Desktop Oriented Version.

package impl.serverpush;

import impl.TestWebSocketServlet;

import java.beans.Statement;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;

/** Tested with ZK 6.5.2<br>
 * 
 * Utilities for ServerPush with WebSocket, concept is similar to
 * ServerPushWithWebSocket -- Component Oriented Version, rewritten it to
 * Desktop Oriented Version.
 * 
 * @author benbai123
 *
 */
@SuppressWarnings("unchecked")
public class ServerPushUtil {
    /** keep all binded Desktop */
    private static List<Desktop> bindedDesktops = new ArrayList<Desktop>();
    /** Lock for push to all desktops */
    private static Integer LOCK_FOR_PUSH_TO_ALL = 0;
    /** Lock for access bindedDesktops */
    private static Integer LOCK_FOR_ACCESS_BINDED_DESKTOPS = 0;
    /** attribute kay for binding map */
    private static final String BINDING_MAP = "BINDING_MAP_FOR_WEBSOCKET_SERVERPUSH";

    /** Add a Binding to a Component
     * 
     * @param comp Component to bind member field with update context
     * @param field field of component to bind
     * @param context context to bind with field
     * @return Binding the added Binding object
     */
    public static Binding addContextBinding (Component comp, String field, String context) {
        Binding binding = null;
        synchronized (comp.getDesktop()) {
            // get binding list of specified component
            List<Binding> bindingList = getBindingList(comp);
            // create a binding object with specified field/context
            binding = new Binding(field, context);
            // add binding into binding list if not exists
            if (!bindingList.contains(binding)) {
                bindingList.add(binding);
            }
        }
        // return created binding
        return binding;
    }
    /** Remove a Binding from a Component
     * 
     * @param comp Component to remove Binding
     * @param field field of the field-context pair to remove
     * @param context context of the field-context pair to remove
     */
    public static void removeContextBinding (Component comp, String field, String context) {
        synchronized (comp.getDesktop()) {
            Map<Component, List<Binding>> bindingMap = getBindingMap(comp.getDesktop());
            // get binding list of specified component
            List<Binding> bindingList = bindingMap.get(comp);
            if (bindingList != null) {
                // create a binding object with specified field/context
                Binding target = new Binding (field, context);
                // remove binding if exists
                for (Binding b : bindingList) {
                    if (b.equals(target)) {
                        bindingList.remove(b);
                        break;
                    }
                }
            }
        }
    }

    /** Push a value to a context for all desktops 
     * 
     * @param value value to push
     * @param context context to push to
     * @throws Exception whatever
     */
    public static void pushVlaue (Object value, String context) throws Exception {
        synchronized (LOCK_FOR_PUSH_TO_ALL) {
            // used to store dead desktop
            List<Desktop> deadDesktops = new ArrayList<Desktop>();
            // make a copy to reduce lock
            List<Desktop> desktopToPush = new ArrayList<Desktop>();
            synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
                desktopToPush.addAll(bindedDesktops);
            }
            // for each binded desktop
            for (Desktop bindedDesktop : desktopToPush) {
                if (bindedDesktop.isAlive()) {
                    // push value to context if alive
                    execPush(value, context, bindedDesktop);
                } else {
                    // store it to deadDesktops otherwise
                    deadDesktops.add(bindedDesktop);
                }
            }
            // remove all dead desktops
            synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
                bindedDesktops.removeAll(deadDesktops);
            }
            desktopToPush.removeAll(deadDesktops);
            for (Desktop dt : desktopToPush) {
                // send response via WebSocket
                TestWebSocketServlet.sendResponse(dt);
            }
        }
    }
    /** Push a value to a context for specified desktop
     * 
     * @param value value to push
     * @param context context to push to
     * @param desktop desktop to apply this push
     * @throws Exception whatever
     */
    public static void pushVlaue (Object value, String context, Desktop desktop) throws Exception {
        if (desktop.isAlive()) {
            // push value to context for specified desktop if alive
            execPush(value, context, desktop);
        } else {
            synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
                // remove specified desktop from bindedDesktops otherwise
                bindedDesktops.remove(desktop);
            }
        }
        // send response via WebSocket
        TestWebSocketServlet.sendResponse(desktop);
    }
    /** Apply "push value to context" for specified desktop
     * 
     * @param value value to push
     * @param context context to push to
     * @param desktop desktop to apply
     * @throws Exception whatever
     */
    public static void execPush (Object value, String context, Desktop desktop) throws Exception {
        // execute push if WebSocket of desktop is ready
        if (TestWebSocketServlet.isWebSocketReady(desktop)) {
            synchronized (desktop) {
                // get binding map
                // (Map<String, List> where String is component ID and List is Binding objects)
                Map<Component, List<Binding>> bindingMap = (Map<Component, List<Binding>>)desktop.getAttribute(BINDING_MAP);
                if (bindingMap != null) {
                    for (Map.Entry<Component, List<Binding>> e : bindingMap.entrySet()) {
                        // get component by ID
                        Component comp = e.getKey();
                        // get binding list
                        List<Binding> bindings = e.getValue();
                        // for each binding
                        for (Binding binding : bindings) {
                            // push value to component if
                            // context of binding is equal to specified context
                            if (binding.getContext().equals(context)) {
                                // get field from binding
                                String field = binding.getField();
                                // build setter name
                                String method = "set" + field.substring(0, 1).toUpperCase() + field.substring(1);
                                // call setter to set value to component
                                Statement stat = new Statement(comp, method, new Object[]{value});
                                stat.execute();
                            }
                        }
                    }
                }
            }
        }
    }
    /** Get Binding list of a Component
     * 
     * @param comp Component to get Binding list
     * @return Binding list for specified Component
     */
    private static List<Binding> getBindingList (Component comp) {
        // get binding map
        Map<Component, List<Binding>> bindingMap = getBindingMap(comp.getDesktop());
        // try to get binding list
        List<Binding> bindingList = bindingMap.get(comp);
        // create and add binding list if not exists
        if (bindingList == null) {
            bindingList = new ArrayList<Binding>();
            bindingMap.put(comp, bindingList);
        }
        // return (created) binding list
        return bindingList;
    }
    /** Get Binding map of a Desktop
     * 
     * @param dt specified Desktop
     * @return Map<Component, List<Binding>> Binding map of specified Desktop
     */
    private static Map<Component, List<Binding>> getBindingMap (Desktop dt) {
        // try to get binding map
        Map<Component, List<Binding>> bindingMap = (Map<Component, List<Binding>>)dt.getAttribute(BINDING_MAP);
        // create and add binding map if not exists
        if (bindingMap == null) {
            bindingMap = new Hashtable<Component, List<Binding>>();
            dt.setAttribute(BINDING_MAP, bindingMap);
            synchronized (LOCK_FOR_ACCESS_BINDED_DESKTOPS) {
                bindedDesktops.add(dt);
            }
        }
        // return (created) binding map
        return bindingMap;
    }
}


zk.xml

Modified from AURequestWithWebSocket, add config for lang-addon.xml, also change the way to get path for WebSocket (line 61~)

https://github.com/benbai123/ZK_Practice/blob/master/Integrate/WebSocket/ServerPushWithWebSocket/WebContent/WEB-INF/zk.xml

lang-addon.xml

Define components used in this POC.

<!-- Tested with ZK 6.5.2
    Define components
-->

<language-addon>
    <addon-name>param</addon-name>
    <language-name>xul/html</language-name>

    <!-- It specifies what language addons this addon
        depends on. If specified, this addon will be
        parsed after all the specified addons are parsed -->
    <depends>zul</depends>

    <!-- define param -->
    <component>
        <component-name>contextBinding</component-name>
        <extends>div</extends>
        <component-class>components.helper.ContextBinding</component-class>
    </component>
    <component>
        <component-name>intbox</component-name>
        <extends>intbox</extends>
        <component-class>components.serverpush.Intbox</component-class>
    </component>
    <component>
        <component-name>textbox</component-name>
        <extends>textbox</extends>
        <component-class>components.serverpush.Textbox</component-class>
    </component>
</language-addon>


Download

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

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