## 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

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

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
-->

<!-- basically you can write something as below,
now assume that you need to
find a typo
or
change some params...
-->
<div>
target="blank">
open chart
</a>
</div>
<div>
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

Just show a possibility that such kind of helper
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>
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
<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>

<!-- Do the same as index.zul with
predefined param component.
-->
<div>
target="blank">
open chart
</a>
</div>
<div>
target="blank" label="open chart, too" />
</div>
<div>
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
<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
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) {
} 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;
}
}
``````

Define 'param' component.

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

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

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>

zk.xml

``````<zk>
<language-config>
</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

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

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

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%)

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
}

// 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) {
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
} 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 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) {
getRegisteredDesktops(null).put(desktop.getId(), desktop);
}
}
/** 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">
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
var _tmWgt = {};

zk.override(zul.utl.Timer.prototype, _tmWgt, {
// onTimer
_tmfn: function () {
if (!this._repeats) this._running = false;
// whether _useWebSocketAU?
if (this._useWebSocketAU
// 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 () {
};
}),
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/