Sunday, September 29, 2013

Python Loop Condition and ErrorHandling


Simple Note

Code

# display message and wait for input
# input is always a string
# ref http://stackoverflow.com/questions/5424716/python-how-to-check-if-input-is-a-number-given-that-input-always-returns-stri
x = input("Please enter an integer, or q to stop: ")
sum = 0
# while input is not 'q'
while x != 'q':
  try: # try to convert x to integer
    x = int(x)
    if x < 0:
      x = 0
      print('Negative changed to zero')
    elif x == 0: # keyword 'elif' is short for 'else if'
      print('Zero')
    else:
      print('positive')
    sum += x
    print('sum = ' + str(sum))
    x = input() # wait for next input
  except ValueError: # error happened when convert x to integer
    x = input("Please enter an integer, or q to stop: ")


Result



References

Python Doc:
http://docs.python.org/3/tutorial/controlflow.html

Python - How to check if input is a number
http://stackoverflow.com/questions/5424716/python-how-to-check-if-input-is-a-number-given-that-input-always-returns-stri

Download

Code at github
https://github.com/benbai123/Python/blob/master/Practice/Basic/flow_control/input_loop_condition_error-handling.py


Sunday, September 22, 2013

Python Basic Math Operators and Functions


Simple Note

Code

# ref:
# number concat string
# http://stackoverflow.com/questions/6981495/how-can-i-concatenate-a-string-and-a-number-in-python
#
# Complex Number
# http://en.wikipedia.org/wiki/Complex_number
#
# Python doc:
# http://docs.python.org/3/tutorial/introduction.html
# http://docs.python.org/3/library/stdtypes.html#typesnumeric
# 

# +, -, *, /
print()
print(" +, -, *, /")
print("    3+3 = " + str(3+3)) # 6
print("    3-3 = " + str(3-3)) # 0
print("    3*3 = " + str(3*3)) # 9
print("    3/3 = " + str(3/3)) # 1.0

# +, -, *, / with ()
print(" +, -, *, / with ()")
print("    (4+5)*3 = " + str((4+5)*3)) # = 9*3 = 27

# negated
print(" negated")
x = 5;
print("    x = 5, -x = " + str(-x)) # -5

# use equal sign to assign value
print(" use equal sign to assign value")
a = 2
b = 3
print("    a=2, b=3, a*b = " + str(a*b)) # 6

# absolute value
print(" absolute value")
print("    abs(5) = " + str(abs(5))) # 5
print("    abs(-5) = " + str(abs(-5))) # 5

# division always returns a floating point number automatically
print(" division always returns a floating point number automatically")
print("    8/5 = " + str(8/5)) # 1.6
print("    32/3 = " + str(32/3)) # 10.666666666666666

# floor division
print(" floor division")
print("    8//5 = " + str(8//5)) # 1
print("    32//3 = " + str(32//3)) # 10

# MOD (the remainder of the division)
print(" MOD (the remainder of the division)")
print("    32%3 = " + str(32%3)) # 2

# the pair (x // y, x % y)
print(" the pair (x // y, x % y)")
print("    divmod(32, 3) = " + str(divmod(32, 3))) # (10, 2)

# floor to specific digits with round()
print(" floor to specific digits with round()")
print("    round(8/3, 2) = " + str(round(8/3, 2))) # 2.67

# complex number with real part re, imaginary part im. im defaults to zero
print(" complex number with real part re, imaginary part im. im defaults to zero")
print("    complex(1, 2) = " + str(complex(1, 2))) # (1+2j)

# power
print(" power")
print("    2**5 = " + str(2**5)) # 32
print("    pow(2, 5) = " + str(pow(2, 5))) # 32

# converted to integer
print(" converted to integer")
print("    int(1.23) = " + str(int(1.23))) # 1

# converted to floating point
print(" converted to floating point")
print("    float(5) = " + str(float(5))) # 5.0


Result



References

number concat string
http://stackoverflow.com/questions/6981495/how-can-i-concatenate-a-string-and-a-number-in-python

Complex Number
http://en.wikipedia.org/wiki/Complex_number

Python doc:
http://docs.python.org/3/tutorial/introduction.html
http://docs.python.org/3/library/stdtypes.html#typesnumeric

Download

Test folder at github
https://github.com/benbai123/Python/tree/master/Practice/Basic/Math/Simple_Calculation

Friday, September 20, 2013

ZK Create Helper Tag to Make Programmer Happier


Introduction

This article describe how to create helper tag by custom component to help programmer to do something easier/better.

NOTE:
This does not mean "this is a good way, follow this pattern"

Just show a possible way that you can give it a try and see if it makes the world better.

Result

Online demo:
http://screencast.com/t/gC2rGFg3KkuU

Program

index.zul

Contains a/button with very long url, also contains a/button/image/ with short url and specify parameters with custom helper component.

<zk>
    <!-- Tested with ZK 6.5.2
        test link available at wiki: http://en.wikipedia.org/wiki/Google_Chart_API
    -->

    <!-- basically you can write something as below,
        now assume that you need to
        find a typo
            or
        change some params...
     -->
    <div>
        <a href="http://chart.apis.google.com/chart?chs=200x200&amp;chdlp=b&amp;chtt=Uberman&amp;chdl=Asleep%7CAwake&amp;chd=t:1,11,1,11,1,11,1,11,1,11,1,11&amp;cht=p&amp;chco=586F8E%7C7D858F"
            target="blank">
            open chart
        </a>
    </div>
    <div>
        <button href="http://chart.apis.google.com/chart?chs=200x200&amp;chdlp=b&amp;chtt=Uberman&amp;chdl=Asleep%7CAwake&amp;chd=t:1,11,1,11,1,11,1,11,1,11,1,11&amp;cht=p&amp;chco=586F8E%7C7D858F"
            target="blank" label="open chart, too" />
    </div>
    <!-- or you can define a helper component to
        make those params clear as below
        to reduce server memory you can specify
        stubonly="true" to helper tags

        Of course this probably is not needed if
        you load those params from DB/Java-Beans and
        build link at server side

        Just show a possibility that such kind of helper
        can help you do some fast prototyping in zul page
        without server side (AP level) support

        And it is possible to change params dynamically so
        probably will be useful when using it with MVVM

        You can also define the helper tag as another component with
        another name in lang-addon.xml, see index_defined.zul
    -->
    <div>
        <a href="http://chart.apis.google.com/chart" target="blank">
            also open chart
            <div name="chs" value="200x200"
                stubonly="true" use="helper.Param" />
            <div name="chdlp" value="b"
                stubonly="true" use="helper.Param" />
            <div name="chtt" value="Uberman"
                stubonly="true" use="helper.Param" />
            <div name="chdl" value="Asleep%7CAwake"
                stubonly="true" use="helper.Param" />
            <div name="chd" value="t:1,11,1,11,1,11,1,11,1,11,1,11"
                stubonly="true" use="helper.Param" />
            <div name="cht" value="p"
                stubonly="true" use="helper.Param" />
            <div name="chco" value="586F8E%7C7D858F"
                stubonly="true" use="helper.Param" />
        </a>
    </div>
    <!-- for components that do not support child, you can
        use give it an ID and specify the ID as target of param
     -->
    <div>
        <button id="btn" label="still open chart" href="http://chart.apis.google.com/chart"
            target="blank" />
        <div name="chs" value="200x200"
            target="btn"
            stubonly="true" use="helper.Param" />
        <div name="chdlp" value="b"
            target="btn"
            stubonly="true" use="helper.Param" />
        <div name="chtt" value="Uberman"
            target="btn"
            stubonly="true" use="helper.Param" />
        <div name="chdl" value="Asleep%7CAwake"
            target="btn"
            stubonly="true" use="helper.Param" />
        <div name="chd" value="t:1,11,1,11,1,11,1,11,1,11,1,11"
            target="btn"
            stubonly="true" use="helper.Param" />
        <div name="cht" value="p"
            target="btn"
            stubonly="true" use="helper.Param" />
        <div name="chco" value="586F8E%7C7D858F"
            target="btn"
            stubonly="true" use="helper.Param" />
    </div>
    <div>
        Display an image
        <image id="img" src="http://chart.apis.google.com/chart" />
        <div name="chs" value="200x200"
            target="img"
            stubonly="true" use="helper.Param" />
        <div name="chdlp" value="b"
            target="img"
            stubonly="true" use="helper.Param" />
        <div name="chtt" value="Uberman"
            target="img"
            stubonly="true" use="helper.Param" />
        <div name="chdl" value="Asleep%7CAwake"
            target="img"
            stubonly="true" use="helper.Param" />
        <div name="chd" value="t:1,11,1,11,1,11,1,11,1,11,1,11"
            target="img"
            id="dynaParam"
            use="helper.Param" />
        <div name="cht" value="p"
            target="img"
            stubonly="true" use="helper.Param" />
        <div name="chco" value="586F8E%7C7D858F"
            target="img"
            stubonly="true" use="helper.Param" />
        and change it dynamically
        <combobox onSelect="dynaParam.setValue(self.getValue());">
            <comboitem label="t:1,11,1,11,1,11,1,11,1,11,1,11" />
            <comboitem label="t:2,10,2,10,2,10,2,10,2,10,2,10" />
        </combobox>
    </div>
</zk>


index_defined.zul

Similar to index.zul, just use the custom component with another name defined in lang-addon.xml.

<zk>
    <!-- test link available at wiki: http://en.wikipedia.org/wiki/Google_Chart_API -->

    <!-- Do the same as index.zul with
        predefined param component.
     -->
    <div>
        <a href="http://chart.apis.google.com/chart?chs=200x200&amp;chdlp=b&amp;chtt=Uberman&amp;chdl=Asleep%7CAwake&amp;chd=t:1,11,1,11,1,11,1,11,1,11,1,11&amp;cht=p&amp;chco=586F8E%7C7D858F"
            target="blank">
            open chart
        </a>
    </div>
    <div>
        <button href="http://chart.apis.google.com/chart?chs=200x200&amp;chdlp=b&amp;chtt=Uberman&amp;chdl=Asleep%7CAwake&amp;chd=t:1,11,1,11,1,11,1,11,1,11,1,11&amp;cht=p&amp;chco=586F8E%7C7D858F"
            target="blank" label="open chart, too" />
    </div>
    <div>
        <a href="http://chart.apis.google.com/chart" target="blank">
            also open chart
            <param name="chs" value="200x200"
                stubonly="true" />
            <param name="chdlp" value="b"
                stubonly="true" />
            <param name="chtt" value="Uberman"
                stubonly="true" />
            <param name="chdl" value="Asleep%7CAwake"
                stubonly="true" />
            <param name="chd" value="t:1,11,1,11,1,11,1,11,1,11,1,11"
                stubonly="true" />
            <param name="cht" value="p"
                stubonly="true" />
            <param name="chco" value="586F8E%7C7D858F"
                stubonly="true" />
        </a>
    </div>
    <div>
        <button id="btn" label="still open chart" href="http://chart.apis.google.com/chart"
            target="blank" />
        <param name="chs" value="200x200"
            target="btn"
            stubonly="true" />
        <param name="chdlp" value="b"
            target="btn"
            stubonly="true" />
        <param name="chtt" value="Uberman"
            target="btn"
            stubonly="true" />
        <param name="chdl" value="Asleep%7CAwake"
            target="btn"
            stubonly="true" />
        <param name="chd" value="t:1,11,1,11,1,11,1,11,1,11,1,11"
            target="btn"
            stubonly="true" />
        <param name="cht" value="p"
            target="btn"
            stubonly="true" />
        <param name="chco" value="586F8E%7C7D858F"
            target="btn"
            stubonly="true" />
    </div>
    <div>
        Display an image
        <image id="img" src="http://chart.apis.google.com/chart" />
        <param name="chs" value="200x200"
            target="img"
            stubonly="true" />
        <param name="chdlp" value="b"
            target="img"
            stubonly="true" />
        <param name="chtt" value="Uberman"
            target="img"
            stubonly="true" />
        <param name="chdl" value="Asleep%7CAwake"
            target="img"
            stubonly="true" />
        <param name="chd" value="t:1,11,1,11,1,11,1,11,1,11,1,11"
            target="img"
            id="dynaParam"
            use="helper.Param" />
        <param name="cht" value="p"
            target="img"
            stubonly="true" />
        <param name="chco" value="586F8E%7C7D858F"
            target="img"
            stubonly="true" />
        and change it dynamically
        <combobox onSelect="dynaParam.setValue(self.getValue());">
            <comboitem label="t:1,11,1,11,1,11,1,11,1,11,1,11" />
            <comboitem label="t:2,10,2,10,2,10,2,10,2,10,2,10" />
        </combobox>
    </div>
</zk>


Param.java

The implementation of custom helper component,

package helper;

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.A;
import org.zkoss.zul.Button;
import org.zkoss.zul.Div;
import org.zkoss.zul.Image;

/** Tested with ZK 6.5.2
 * 
 * @author benbai123
 *
 */
public class Param extends Div {

    private static final long serialVersionUID = 8842930664701238051L;

    private String _name = "";
    private String _value = "";
    private String _target;
    private String _oldValue = "";

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Param () {
        // update target url while created
        addEventListener(Events.ON_CREATE, new EventListener () {
            public void onEvent (Event event) {
                updateTargetUrl();
            }
        });
        // do not output any html
        setWidgetOverride ("redraw", "function (out) {}");
    }
    // setters
    public void setTarget (String target) {
        _target = target;
    }
    public void setName (String name) {
        if (name == null) {
            name = "";
        }
        _name = name;
    }
    public void setValue (String value) {
        if (value == null) {
            value = "";
        }
        _value = value;
        updateTargetUrl();
    }

    private void updateTargetUrl () {
        if (!_name.isEmpty() && !_value.isEmpty()) {
            Component target = findTarget();
            if (target instanceof A) {
                A link = (A)target;
                String url = updateUrl(link.getHref());
                link.setHref(url);
            } else if (target instanceof Button) {
                Button btn = (Button)target;
                String url = updateUrl(btn.getHref());
                btn.setHref(url);
            } else if (target instanceof Image) {
                Image img = (Image)target;
                String url = updateUrl(img.getSrc());
                img.setSrc(url);
            }
        }
    }
    // try to find target to update
    private Component findTarget () {
        if (_target != null && !_target.isEmpty()) {
            return getParent().getFellowIfAny(_target);
        } else if (getParent() instanceof A) {
            return getParent();
        }
        return null;
    }
    // update url with param/value
    private String updateUrl (String url) {
        String value;
        if (url.indexOf("?") == -1) {
            url += "?";
        } else {
            url += "&";
        }
        value = _name + "=" + _value;
        // replace old value or append new value at tail
        if (!_oldValue.isEmpty()) {
            url.replace(_oldValue, value);
        } else {
            url += value;
        }
        return url;
    }
}


lang-addon.xml

Define 'param' component.

<!-- Tested with ZK 6.5.2
    Define param component
-->

<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>param</component-name>
        <!-- extends div and change java class -->
        <extends>div</extends>
        <component-class>helper.Param</component-class>
    </component>
</language-addon>


zk.xml

Load lang-addon.xml.

<zk>
    <!-- load lang-addon.xml -->
    <language-config>
        <addon-uri>/WEB-INF/lang-addon.xml</addon-uri>
    </language-config>
</zk>


References

Language Definition
http://books.zkoss.org/wiki/ZK_Client-side_Reference/Language_Definition

The language-config Element
http://books.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_language-config_Element

Download

Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Components/projects/Components_Customization/ParamTagForUrl

Demo Flash

Sunday, September 8, 2013

File Access in Python


Simple Note

Code

import os

# get path of the folder contains this script
pathToCurrentFolder = os.path.dirname(os.path.abspath(__file__))
# open test.txt in read mode
f = open(pathToCurrentFolder + '/test_files/test.txt', 'r')
# create output.txt (write mode)
o = open(pathToCurrentFolder + '/test_files/output.txt', 'w')
# create output2.txt (write mode)
o2 = open(pathToCurrentFolder + '/test_files/output2.txt', 'w')

idx = 1

print('current folder: ' + pathToCurrentFolder)

# get all content of test.txt
content = f.read()

print('write all content to test_files/output.txt')
# write content to output.txt
o.write("content from test.txt: " + content)

# go to head of test.txt
f.seek(0)

print('write each line with line number to test_files/output2.txt')
# for each line in test.txt
# write line with line number into output2.txt
for line in f:
    o2.write(str(idx) + '\t' + line)
    idx += 1
# or call o2.write(f.readline()) three times


Reference

How to get full path of current directory in Python?
http://stackoverflow.com/questions/3430372/how-to-get-full-path-of-current-directory-in-python

Reading and Writing Files
http://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files

Download

Test folder at github
https://github.com/benbai123/Python/tree/master/Practice/Basic/file_access

WebSocketAU vs ZKAU Performance Testing


Simple Note

Testing Steps

1. Start Tomcat

2. Open visualVM

3. Open 5 testing pages (index.zul)

4. Wait 2 minutes

5. Take snapshot

Testing Result

Result of WebSocket AU



Result of ZKAU



Conclutions

Memory Consumption: Almost the same

CPU Usage: WebSocket AU (~15% then 1% ~ 3%) is better than ZK AU (~33% than 2% ~ 13%)

Download

Testing project at github
https://github.com/benbai123/ZK_Practice/tree/master/General/Performance_Testing/WebSocketAU_vs_ZKAU

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