<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-3000236639335370164</id><updated>2012-01-28T23:24:35.017+11:00</updated><category term='LINQ'/><category term='GWT'/><category term='Architecture'/><category term='Closure'/><category term='Cloud Computing'/><category term='web'/><category term='REST'/><category term='Design'/><category term='Management'/><category term='Java'/><category term='Apple'/><category term='SOA'/><category term='Web Service'/><category term='RIA'/><category term='Groovy'/><category term='Symbian'/><category term='JavaFX'/><category term='JavaME'/><category term='Flash'/><category term='WCF'/><category term='Chrome'/><category term='Database'/><category term='Framework'/><category term='LinkedIn'/><category term='ORM'/><category term='SDP'/><category term='Hacking'/><category term='Telecommunications'/><category term='Android'/><category term='Facebook'/><category term='WPF'/><category term='N95'/><category term='Entity Framework'/><category term='ZK'/><category term='.NET'/><category term='Erlang'/><title type='text'>Romen's Techno-Babble</title><subtitle type='html'>e海拾贝 - My public blog space on technical subjects - software development, tools &amp;amp; frameworks and technology management.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default?start-index=101&amp;max-results=100'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>109</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4836223405608766758</id><published>2012-01-04T16:44:00.001+11:00</published><updated>2012-01-04T17:16:39.737+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><title type='text'>A Simple Property Viewer with Reflection and ZK</title><content type='html'>&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
&lt;div dir="ltr" style="text-align: left;" trbidi="on"&gt;
Sometimes I need to display a POJO’s properties/attributes. These POJOs may contain dozens of properties making it tedious to invoke all the &lt;code&gt;getXxx()&lt;/code&gt; methods. So Java Reflection is a perfect candidate for such a use case.&lt;br /&gt;
In the following example, the &lt;code&gt;LoginResponse&lt;/code&gt; class contains dozens of fields that I want to display. The &lt;code&gt;formatMethodName()&lt;/code&gt; method beautifies the &lt;code&gt;getXxx()&lt;/code&gt; method names into more human user friendly names. 
&lt;br /&gt;
&lt;pre class="java" name="code"&gt;
import java.lang.reflect.Method;
import com.laws.acc.ws.*;
import com.laws.acc.model.*;

LoginResponse loginResponse=AccSoapClientProxy.getLoginResponse();
void populateRows() {  
	if(loginResponse!=null) {
		Method[] methods = LoginResponse.class.getDeclaredMethods();
		for(int i=0; i&amp;lt;methods.length; i++) {
			
			
			Method method=methods[i];
			//Object arglist[]={};
			if(method.getName().startsWith("get")) {
				Row row=new Row();
				Label label=new Label(formatMethodName(method.getName()));
				label.setStyle("font-style: italic;");
				label.setParent(row);
				new Label(method.invoke(loginResponse, null).toString()).setParent(row);
				row.setParent(rows);
			}
		}
	}
}
/**
 * turn strings such as 'getUserName' into 'User Name'
 */
private String formatMethodName(String name) {
	// the method name should start with 'get' followed by the property name
	// otherwise, ignore the method.
	if(name==null || name.length()&amp;lt;4) return null;
	StringBuffer sb=new StringBuffer();
	
	// skip the first 3 characters, they are 'get'
	for(int i=3; i&amp;lt;name.length(); i++) {
		char ch = name.charAt(i);
		if(i&amp;gt;3 &amp;&amp; ch&amp;gt;='A' &amp;&amp; ch&amp;lt;='Z') {
			sb.append(" ");
		}
		sb.append(ch);
	}
	return sb.toString().replace("Id", "ID").replace("Dt", "Timestamp").replace("Lre", "LRE").replace("Mvno", "MVNO");
	
}
&lt;/pre&gt;
The GUI component I am using is a &lt;a href="http://books.zkoss.org/wiki/ZK_Component_Reference/Data/Grid"&gt;ZK Grid&lt;/a&gt; with each row showing a property value.   
&lt;br /&gt;
&lt;pre class="xml" name="code"&gt;&lt;zk xmlns:h="http://www.w3.org/1999/xhtml" xmlns:zk="http://www.zkoss.org/2005/zk"&gt;
&lt;window border="normal" closable="true" height="450px" id="winUserInfo" mode="modal" position="center" sizable="true" title="User Info" width="400px"&gt;
	&lt;zscript&gt;
// put the above java code here.
&lt;/zscript&gt;
	
&lt;borderlayout&gt;
&lt;center autoscroll="true"&gt;
	&lt;label if="${loginResponse==null}" value="User information is not available."&gt;&lt;/label&gt;
	&lt;grid if="${loginResponse!=null }"&gt;
		&lt;columns&gt;&lt;column&gt;&lt;/column&gt;&lt;column&gt;&lt;/column&gt;&lt;/columns&gt;
		&lt;rows id="rows"&gt;
		&lt;zscript&gt;populateRows();&lt;/zscript&gt;
	&lt;/rows&gt;&lt;/grid&gt;
&lt;/center&gt;&lt;label if="${loginResponse==null}" value="User information is not available."&gt;&lt;/label&gt;
&lt;south&gt;
	&lt;div align="right"&gt;
&lt;button label="Close" onclick="winUserInfo.detach()"&gt;
	
&lt;/button&gt;&lt;/div&gt;
&lt;/south&gt;
&lt;/borderlayout&gt;&lt;/window&gt;&lt;/zk&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;span style="font-size: xx-small;"&gt;&lt;span style="font-size: xx-small;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4836223405608766758?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4836223405608766758/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4836223405608766758' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4836223405608766758'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4836223405608766758'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2012/01/simple-property-viewer-with-reflection.html' title='A Simple Property Viewer with Reflection and ZK'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4461097296872645757</id><published>2012-01-04T16:29:00.001+11:00</published><updated>2012-01-04T16:29:48.767+11:00</updated><title type='text'>Server Error 500 on Saturday Lotto!</title><content type='html'>&lt;p&gt;I tried to check my New Year’s eve NSW Saturday Lotto results on their official website, and one group of my numbers consistently resulted in server error 500.&lt;/p&gt;  &lt;p&gt;Try this: &lt;a title="http://www.nswlotteries.com.au/cgi-bin/results.pl?numlookup=1&amp;amp;game=satlotto&amp;amp;draw=3177&amp;amp;num1=4&amp;amp;num2=37&amp;amp;num3=1&amp;amp;num4=2&amp;amp;num5=3&amp;amp;num6=6" href="http://www.nswlotteries.com.au/cgi-bin/results.pl?numlookup=1&amp;amp;game=satlotto&amp;amp;draw=3177&amp;amp;num1=4&amp;amp;num2=37&amp;amp;num3=1&amp;amp;num4=2&amp;amp;num5=3&amp;amp;num6=6"&gt;http://www.nswlotteries.com.au/cgi-bin/results.pl?numlookup=1&amp;amp;game=satlotto&amp;amp;draw=3177&amp;amp;num1=4&amp;amp;num2=37&amp;amp;num3=1&amp;amp;num4=2&amp;amp;num5=3&amp;amp;num6=6&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;It turns out that as long as the group of numbers only match the supplementary numbers and nothing else, then it will result in errors. In the above example, the actual result of the Lotto draw was: 7, 8, 28, 33, 38, 41 with supplementary numbers 4 and 37. &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4461097296872645757?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4461097296872645757/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4461097296872645757' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4461097296872645757'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4461097296872645757'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2012/01/server-error-500-on-saturday-lotto.html' title='Server Error 500 on Saturday Lotto!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5092392598298271362</id><published>2011-10-30T11:55:00.001+11:00</published><updated>2011-10-30T11:55:57.589+11:00</updated><title type='text'>On RSA Hack …</title><content type='html'>&lt;p&gt;Recently our IT system admin surveyed the company about some changes on how we would use the &lt;a href="http://www.rsa.com/node.aspx?id=1159"&gt;RSA SecureID&lt;/a&gt;. As I was totally oblivious about the &lt;a href="http://www.zdnet.com.au/rsa-confirms-attack-was-state-sponsored-339324225.htm"&gt;RSA hacking&lt;/a&gt; incident, I did not pay much attention to the survey. Then earlier this month when I was doing a product demo at a client site, I found that my company VPN login stopped working half way during the demo, and it remained so for the next couple of days until I rang up IT support and got it reset. Obviously, as a precautionary action, the sys admin had changed the &lt;a href="http://www.arrowecs.cz/web/drivery.nsf/0/8a7923c16e3fa2d3c125754e0052f3a6/$FILE/RADIUS_Ref.pdf"&gt;RSA RADIUS&lt;/a&gt; policy …&lt;/p&gt;  &lt;p&gt;Although the RSA hacking incident and the &lt;a href="http://krebsonsecurity.com/2011/10/who-else-was-hit-by-the-rsa-attackers/?utm_source=feedburner&amp;amp;utm_medium=feed&amp;amp;utm_campaign=Feed%3A+KrebsOnSecurity+%28Krebs+on+Security%29&amp;amp;utm_content=Google+Reader"&gt;related APT attacks&lt;/a&gt; did not affect my company, it is enough to get the IT department worried. In the &lt;a href="http://www.rsa.com/node.aspx?id=3872"&gt;Open Letter to RSA Customers&lt;/a&gt;, RSA called the attack ‘extremely sophisticated’. However, knowing that it comes from the Chairman of RSA, you have to take it with a grain of salt. In the blog &lt;a href="http://blogs.rsa.com/rivner/anatomy-of-an-attack/"&gt;Anatomy of an Attack&lt;/a&gt; by Uri Rivner from RSA, we gain some insight into what had happened at a very high level. I find nothing new in the method of the attack though. However, there are a couple of points that bring a smirk or two.&lt;/p&gt;  &lt;p&gt;First is the use of &lt;a href="http://en.wikipedia.org/wiki/Reverse_connection"&gt;reverse-connect&lt;/a&gt; mode of the backdoor software to circumvent the firewall at the remote end. This reminds me of the bad/good old days before the proliferation of the internet. My job was to develop &lt;a href="http://en.wikipedia.org/wiki/Interactive_voice_response"&gt;IVRs&lt;/a&gt; for customers across many industries. To maintain the system, we would need to connect to them remotely. In those days, the connection method of choice was via good old PSTN line modems, which are plugged into the IVR system. The ‘normal’ mode of connection would be to dial up to the modem and login (using the UNIX &lt;a href="http://compute.cnr.berkeley.edu/cgi-bin/man-cgi?cu+1"&gt;cu command&lt;/a&gt;). However, to save on long-distance call cost, we usually buried hidden option in the IVR menu. Upon entering a code, the IVR would invoke some shell script to get the remote modem to dial back to the office modem and give a login prompt upon connection – using the UNIX &lt;a href="http://compute.cnr.berkeley.edu/cgi-bin/man-cgi?ct+1"&gt;ct command&lt;/a&gt;. This use of reverse connection is totally legit, by the way. &lt;/p&gt;  &lt;p&gt;The second and more obvious point is the use of social engineering as the starting point. We have seen from spy movies time and time again that human is the weakest link in the line of defence. This social engineering is made extremely easy and cheap today, thanks to the massive adoption of social network services. This is part of the reason I doubt the words of ‘extremely sophisticated’ coming from RSA Chairman. It is extremely easy to find out someone’s personal details from their social network web pages, and find a list of friends that they trust. This makes it extremely easy to spoof an email with backdoor attached and have a high probability of it getting viewed and opened. Once the backdoor is in, all bets are off. You don’t have to be ‘extremely sophisticated’ to snoop around and gain more data – e.g. most people use their mailbox as a file repository, when someone has access to the Outlook PST file, then they can see a history of everything. This is partly the reason I never use thick mail clients and use the webmail instead – after all, it is the era of the cloud man! &lt;img style="border-bottom-style: none; border-left-style: none; border-top-style: none; border-right-style: none" class="wlEmoticon wlEmoticon-smile" alt="Smile" src="http://lh5.ggpht.com/-1_P5OS4JGPA/Tqygm0m14fI/AAAAAAAAADo/Ux0p0_jVr9M/wlEmoticon-smile%25255B2%25255D.png?imgmax=800" /&gt;&lt;/p&gt;  &lt;p&gt;Another interesting statistics is by looking at &lt;a href="http://krebsonsecurity.com/2011/10/who-else-was-hit-by-the-rsa-attackers/?utm_source=feedburner&amp;amp;utm_medium=feed&amp;amp;utm_campaign=Feed%3A+KrebsOnSecurity+%28Krebs+on+Security%29&amp;amp;utm_content=Google+Reader"&gt;Who Else Was Hit by the RSA Attackers&lt;/a&gt;. Apparently, similar attacks have happened to hundreds of other companies recently and close to 90% of the attacks came from Beijing. No wonder people conclude that the attacks were state sponsored! China is a country where censorship is absolutely in every medium. The mass majority of the netizens are subjected to the &lt;a href="http://en.wikipedia.org/wiki/Great_Firewall_of_China"&gt;GFW of China&lt;/a&gt;, which dynamically blocks destinations based on myriad of rules, including a government issued list of sensitive keywords. So in a country where vast number of web addresses are inaccessible to the normal citizens (including many social websites, and even this blogger site), only the privileged and well resourced can carry out such ‘extremely sophisticated’ attacks.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5092392598298271362?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5092392598298271362/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5092392598298271362' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5092392598298271362'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5092392598298271362'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/10/on-rsa-hack.html' title='On RSA Hack …'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/-1_P5OS4JGPA/Tqygm0m14fI/AAAAAAAAADo/Ux0p0_jVr9M/s72-c/wlEmoticon-smile%25255B2%25255D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5934085329175313111</id><published>2011-09-16T14:38:00.002+10:00</published><updated>2011-10-30T12:10:39.566+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Oracle vs. Google Lawsuit</title><content type='html'>&lt;p&gt;I have read news about Oracle’s lawsuit against Google regarding its Android platform, alleging that Google is copying from Java. Some had even concluded that Android is in jeopardy. I had not paid much attention to the details of the lawsuit, until today when I came across Google’s rebuttal by a technical expert Prof. Owen Astrachan of Duke University. The report is published on &lt;a href="http://www.groklaw.net/article.php?story=20110907141407472"&gt;Groklaw&lt;/a&gt; - it’s really fun to read.&lt;/p&gt;&lt;p&gt;The central issue of the lawsuit seems to be whether the Java API (or API of any language, library, even C-header files) are copyrightable. Google’s argument is that APIs are not creative expression, hence not copyrightable; but the implementation of the APIs are. Prof. Owen gave strong arguments backed by facts and through analogies. &lt;/p&gt;&lt;p&gt;Surely, anyone who has done software programming would not consider interfaces copyrightable. What do you think? – click your preference in the poll on the upper right hand corner of this page to express your view.&lt;/p&gt;&lt;p&gt;&lt;b&gt;Updated 2011-10-30:&lt;/b&gt; the verdict is out. Total votes: 6; Yes vote: 5 (83%); No vote: 1 (16%).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5934085329175313111?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5934085329175313111/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5934085329175313111' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5934085329175313111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5934085329175313111'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/09/oracle-vs-google-lawsuit.html' title='Oracle vs. Google Lawsuit'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-384157973747087067</id><published>2011-07-31T11:22:00.001+10:00</published><updated>2011-08-01T09:37:35.782+10:00</updated><title type='text'>The Power of Openness</title><content type='html'>&lt;p&gt;I was going to name this blog “why Samsung TVs are much better than Sony’s”. However, it may sound out of place in a technical blog focusing on computing and telco technologies.&lt;/p&gt;  &lt;p&gt;As I have blogged recently about the issues of openness vs. closeness – e.g. Android vs. iOS, the same issue manifests itself in the broader consumer world, such as flat screen TVs.&lt;/p&gt;  &lt;p&gt;With Australian government’s decision of moving from analogue TV to digital, the new flat screen TVs are selling like hot cakes. The available choices are dazzling – each boasts their own technical advantages in terms of picture quality, energy saving, sound quality etc. Your average consumers cannot care about all the technical mumbo jumbos, all they want is a TV! But is it?&lt;/p&gt;  &lt;p&gt;As the display technology advances and matures, all the major manufacturers from all the major countries can produce decent display units/screens. So there is no point trying to analyse which technology is better in this regard – at the end of the day, what ever looks and sounds pleasing to you should win. So do those manufactures differ in any way? Yes they do – they differ in how open they are in terms of embracing different technologies.&lt;/p&gt;  &lt;p&gt;Nowadays, it is common to record and view your favourite HD TV programs and movies from USB memory sticks or hard disks. A HD video can take any where from 1GB to 10GB of space. As we know, the major file systems supported by the TV manufactures are &lt;a href="http://en.wikipedia.org/wiki/Fat32#FAT32"&gt;FAT/FAT32&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Ntfs"&gt;NTFS&lt;/a&gt;. With FAT32, there is an upper limit on the file size, being 4GB-1Byte. This basically rules out FAT or FAT32 as a valid format for your USB storage. This is where it matters – manufactures such as Sony and Panasonic do not support anything other than FAT/FAT32 on their TV and BluRay players (or USB 1 devices); whereas Samsung and most Chinese brands do. &lt;/p&gt;  &lt;p&gt;There is good reason for Sony to be restrictive. It owns media companies too – Sony Pictures. It’s in their interest to stop you from recording or backing up your movies into one place to make things more convenient. So it is the same theme being played out in the computing industry for decades. Consumers can cast their vote with their hard-earned currency – the classical supply-demand interplay. &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-384157973747087067?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/384157973747087067/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=384157973747087067' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/384157973747087067'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/384157973747087067'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/07/power-of-openness.html' title='The Power of Openness'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-3019222288667473600</id><published>2011-07-22T11:21:00.001+10:00</published><updated>2011-08-18T16:55:41.830+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Telecommunications'/><category scheme='http://www.blogger.com/atom/ns#' term='Cloud Computing'/><title type='text'>Getting Cloudy</title><content type='html'>&lt;p&gt;Last week I attended the &lt;a href="http://www.eventsetter.com/events/Australia/New-South-Wales/Sydney/Conferences-Conventions/event136683.html"&gt;Amazon Web Services APAC Tour in Sydney&lt;/a&gt;. It was quite helpful to see the AWS in action and their customer testimonials in real life. &lt;/p&gt;  &lt;p&gt;There is no doubt that the cloud environment can help many IT departments and internet-oriented businesses. The main benefits are cost reduction and improved scalability and reliability. Unlike many IT fads in previous years, there is real momentum and benefits of adopting the cloud approach for many businesses and government organisations alike.&lt;/p&gt;  &lt;p&gt;When it comes to telco BSS and OSS applications, the use case is quite different from many other industries. At the bottom level, many BSS and OSS applications need to be connected to the network in real-time. This can be real-time charging, service control for BSS and real-time network performance management for OSS. These real-time interfaces are usually dedicated links using telco standards (e.g. SS7 based), which conventional cloud providers do not support. So you will have no choice but to host these applications locally. &lt;/p&gt;  &lt;p&gt;For the non-real-time (i.e. offline) telco applications, many of them need to access the vast repository of usage records, transaction records and network events. A telco may choose to host only the front-office web applications in the cloud and have them retrieve the necessary data stored locally over the internet; or take advantage of the cloud storage and ship all the transaction data to the cloud over the internet in a batch mode frequently. Either way, the requirement on the WAN link between the telco and the cloud data centers is very high. Let’s assume 1 million subscribers with 0.5BHCA and the CDR size at 750 bytes (note that CDRs from the &lt;a href="http://www.3gpp.org/ftp/Specs/archive/32_series/32.225/32225-5b0.zip"&gt;3GPP Bi&lt;/a&gt; interface may be much larger). This produces 375MB of CDR data a day. When it comes to OSS network management, the alarm repository can easily go into gigabytes and terabytes.&lt;/p&gt;  &lt;p&gt;There may be a compelling business case for small telcos who do not have large amount of data and a slow growth model. Still, reliable WAN link with high bandwidth is required. These links may not be cheap and are charged on an ongoing basis – unlike the local computer servers which are one-off expenditure. &lt;/p&gt;  &lt;p&gt;On the contrary, large telcos do not have problem with network links because in many cases, they own the network. Yet their data volume can also be orders of magnitude higher than the small ones. If you are not physically located in the same country or continent with their cloud provider’s data centers, then having a reliable WAN link becomes even more expensive if it’s possible at all – because the submarine cables are owned by consortiums, not any single telcos.&lt;/p&gt;  &lt;p&gt;Also, beware of the pricing model of the cloud providers. They charge for each virtual machine instance, each I/O, storage, etc. The other day, I played with AWS EC2 by creating a supposedly free Linux based micro-instance, and snooping around by changing directories and doing&amp;#160; ‘ls’, etc. before terminating the instance. To my surprise, there were total of 13,473 I/Os, which I could not account for. I got charged for a total of 5 cents for less than an hour of activities (most idle any way). So, take a close look at the pricing model of the cloud provider, it may turn out to be much cheaper to buy your own Windows servers to host your Outlook than doing it in the cloud.&lt;/p&gt;  &lt;p&gt;So a middle ground solution is for a telco to host their own cloud environment and use this for their own BSS/OSS systems as well as providing cloud services to their customers. Quite a few telcos have already taken this route. Of course, hosting a cloud is expensive in terms of CAPEX and OPEX. There must be a good business case to do so.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Related Articles:&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://www.theserverside.com/news/thread.tss?thread_id=62716"&gt;Here are a few reasons why *not* to use the cloud&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://techcrunch.com/2011/08/08/amazon-ec2-outage/"&gt;Down Goes The Internet… Again. Amazon EC2 Outage Takes Down Foursquare, Instagram, Quora, Reddit, Etc&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.zdnet.com/blog/microsoft/outage-hits-microsoft-crm-online-office-365-customers/10359"&gt;Outage hits Microsoft CRM Online, Office 365 customers&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-3019222288667473600?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/3019222288667473600/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=3019222288667473600' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3019222288667473600'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3019222288667473600'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/07/getting-cloudy.html' title='Getting Cloudy'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-2529621500465098428</id><published>2011-06-22T10:14:00.001+10:00</published><updated>2011-06-26T11:53:40.063+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Apple'/><title type='text'>Apple Development</title><content type='html'>&lt;p&gt;When it comes to software development, I am absolutely an omnivore. I embrace any platform, computing languages and frameworks. I use them to develop my pet projects to form my basis of evaluating the technology.&lt;/p&gt;  &lt;p&gt;However, I had never touched any Apple technology until last month when my wife twisted my arm to &lt;a href="http://romenlaw.blogspot.com/2011/05/rip-symbian-hello-iphone-4.html"&gt;buy an iPhone 4&lt;/a&gt;.&amp;#160; Not because I dislike Apple’s philosophy and its snobbish ways, but because it was so irrelevant for me. But after I bought the iPhone 4 I got the urge of developing on the iOS and the iPhone so that my $1000+ investment on the iPhone was not a total waste. &lt;/p&gt;  &lt;p&gt;So I happily registered to be an &lt;a href="http://developer.apple.com/devcenter/ios/index.action"&gt;Apple Developer&lt;/a&gt;. Then I needed to get the SDK, an iOS simulator and and IDE. &lt;a href="http://developer.apple.com/xcode/index.php"&gt;Xcode 4&lt;/a&gt; was about the only tool available. When I logged in and ready to download Xcode 4, I was told:&lt;/p&gt; &lt;code&gt;&lt;b&gt;Hi Romen&lt;/b&gt;,     &lt;p&gt;You must be an iOS or Mac Developer Program member to download Xcode 4 or you can purchase Xcode 4 from the Mac App Store. &lt;/p&gt; &lt;/code&gt;  &lt;p&gt;The membership costs $99 a year. This is not an option for I just want to try it out. The Xcode 4 costs $4.99 from the AppStore – hmm, not bad though the customer review was not very flattering… Then I realised that Xcode can only run on Mac OS X, which means I would have to fork out another $1000+ to buy an Apple computer just for using Xcode. I don’t mind an Apple computer, but there is no justification for its doubled price comparing to other brands with &lt;a href="http://mattrichman.tumblr.com/post/6844151919/a-consequence-of-losing-the-pc-wars"&gt;gross margin at 7 times of others&lt;/a&gt;. I do have an Mac OS X virtual machine image on VMWare, but it was too slow just to run itself, let alone Xcode. This reminds me of the early 1990s when you had to pay left and right just to learn a computing language, which was especially touch for students like me.&lt;/p&gt;  &lt;p&gt;Also, once you finish developing your app, you’d have to go through Apple Store to sell them and get exploited by Apple again. Apple does not do a good job to keep a high quality of what they sell anyway – we only had the iPhone 4 for 3 weeks and already experienced buggy apps – buttons on the default SMS app and Facebook app were greyed out when they shouldn’t be and can only be fixed by bouncing the software or rebooting the phone (by the way, I do not see any rationale in Apple’s decision of not letting the user exit each app.)&lt;/p&gt;  &lt;p&gt;The barrier to entry seems too high for a private user. I am used to the open source environments of many innovative technologies (led by Java perhaps). For me personally to buy into the Apple’s closed-door philosophy is just too much. So I have to ditch Apple technology as I always have.&lt;/p&gt;  &lt;p&gt;There is plenty to explore in the Android domain.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-2529621500465098428?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/2529621500465098428/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=2529621500465098428' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2529621500465098428'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2529621500465098428'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/06/apple-development.html' title='Apple Development'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8865845939049484290</id><published>2011-06-03T12:44:00.001+10:00</published><updated>2011-06-03T12:47:14.522+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Apple'/><title type='text'>Must-Haves On A New ₳₱₱£€ iPhone 4</title><content type='html'>&lt;p&gt;The whole ₳₱₱£€ experience from start to end, from developer to user, from manufacturer to partner is just a big money grabbing exercise by Apple at every step involved. ₳₱₱£€ not only asks you to pay left and right but also restricts on what you can do and how you do things. To get back some sanity when using the new iPhone 4, here are some things that have to be done:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;strong&gt;Jailbreak it&lt;/strong&gt; – for iPhone 4 running iOS 4.3.3 (8J2) &lt;a href="http://www.youtube.com/watch?v=s4YZhP0uZJ4"&gt;here&lt;/a&gt; is a good starter. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Change root password&lt;/strong&gt; – after Jailbreaking, the phone’s (running BSD Unix) root password is automatically set to ‘alpine’. This must be changed. Note that the MobileTerminal app does not work on iOS 3 and 4 at the time of this post. So to do this, it is better to install &lt;a href="http://cydia.saurik.com/openssh.html"&gt;OpenSSH&lt;/a&gt; and use a client such as &lt;a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html"&gt;PuTTY&lt;/a&gt; to connect to the iPhone with root login and use the &lt;code&gt;passwd&lt;/code&gt; Unix command to change the root password. &lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Ditch iTune&lt;/strong&gt; – iTune forces you to sync apps between iPhone and the computer. This is not only waste of time and the computer disk space, but also creates problems when there are multiple computers. &lt;a href="http://www.iphone-without-itunes.com/"&gt;CopyTrans&lt;/a&gt; is a good solution to this.&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;Get a decent GPS app&lt;/strong&gt; – the default map application assumes that the phone is connected to the internet all the time, which can be extremely costly with many carriers and impractical in many countries. So there is a need to have a GPS app that can store map files on the iPhone locally. &lt;a href="http://xgps.xwaves.net/index.php?title=Main_Page"&gt;xGPS&lt;/a&gt; seems to fit the bill, although it cannot be used fully offline (like TomTom or Nokia Maps) – you’ll still need to be connected to Google when searching for addresses and working out routes.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;As I am totally new to any ₳₱₱£€ product, any free apps suggestions are welcome.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8865845939049484290?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8865845939049484290/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8865845939049484290' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8865845939049484290'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8865845939049484290'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/06/must-haves-on-new-iphone-4.html' title='Must-Haves On A New ₳₱₱£€ iPhone 4'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-826014055553062582</id><published>2011-05-31T22:04:00.001+10:00</published><updated>2011-05-31T22:04:03.516+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Symbian'/><category scheme='http://www.blogger.com/atom/ns#' term='Apple'/><title type='text'>RIP Symbian, Hello iPhone 4</title><content type='html'>&lt;p&gt;The joint &lt;a href="http://blogs.technet.com/b/microsoft_blog/archive/2011/04/21/one-step-closer-to-the-first-nokia-device-built-on-windows-phone.aspx"&gt;announcement&lt;/a&gt; from Nokia’s Kai Öistämö and Microsoft’s Andy Lees last month had formally declared the death of Symbian. The new N8’s and E7’s may well be the last emperors of the Symbian dynasty. &lt;/p&gt;  &lt;p&gt;In reality, the Symbian applications community has been languishing for a long time, far behind the new comers. Trying to find a decent internet radio software today, the only option I could find was Nokia Internet Radio. I installed in on my N95 and it soon crashed my phone and caused it to reboot. Such problems have been reported back in 2008. Yet, it seems that nothing has been done. &lt;/p&gt;  &lt;p&gt;On the other hand there are far more software in the iPhone and Android communities. Take internet radio for example, &lt;a href="http://tunein.com/"&gt;TuneIn&lt;/a&gt; supports just about any mobile OS except Symbian. It has access to over 40,000 stations and runs very smoothly on my new iPhone 4. It’s a far cry from the pathetic Nokia Internet Radio.&lt;/p&gt;  &lt;p&gt;I had never owned any Apple product until yesterday – after some nagging I bought a white iPhone 4 for Rose. I always thought Apple products were for women and children for two reasons:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;they look cool&lt;/li&gt;    &lt;li&gt;they are easy to use&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;But after playing with the iPhone 4, I changed my mind – it’s not easy to use at all! It’s just as bad as any other products, if not worse. &lt;/p&gt;  &lt;p&gt;First of all, you cannot even start using the iPhone without a MicroSIM card. At least, my trusted N95 could work without SIM card and function normally except for telco services. So the first hurdle for me was to cut my normal SIM card with scissors and make it into a MicroSIM. I was amazed that it actually worked!.&lt;/p&gt;  &lt;p&gt;After that, I had to register on AppStore by providing my private information. Even if you want to install a free software, you are still required to put in your credit card detail - what the heck?!&lt;/p&gt;  &lt;p&gt;Then I found that the iPhone refused to load any of my music files that were accumulated over the years – not even the polyphonic ring tone that I have been using for years. That was the final straw. I had no option but to &lt;a href="http://www.youtube.com/watch?v=s4YZhP0uZJ4"&gt;jailbreak&lt;/a&gt;, after which the whole user experience became much more acceptable. At least the process was much easier and quicker than cracking my N95.&lt;/p&gt;  &lt;p&gt;Now the women and children in my household can better enjoy the over hyped iPhone 4 before they get bored with it, just like the old XM5800.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-826014055553062582?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/826014055553062582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=826014055553062582' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/826014055553062582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/826014055553062582'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/05/rip-symbian-hello-iphone-4.html' title='RIP Symbian, Hello iPhone 4'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-104308026339059548</id><published>2011-05-17T12:33:00.001+10:00</published><updated>2011-05-17T12:33:27.393+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Telecommunications'/><title type='text'>Throttling</title><content type='html'>&lt;p&gt;A while ago, I blogged about the &lt;a href="http://romenlaw.blogspot.com/2010/08/nbn-australia.html"&gt;NBN Australia&lt;/a&gt; and how much benefits that city folks like myself will (not) receive. There is another little quirk that NBN Co. never mentions – throttling.&lt;/p&gt;  &lt;p&gt;Throttling refers to the traffic control measure applied by all ISPs and telcos to limit the speed/bandwidth available to the end users – squeezing the pipe, if you like. &lt;/p&gt;  &lt;p&gt;Have you ever wondered why your Limewire/Frostwire bandwidth is so low (as low as a few Kbps – lower than dial-up speed) although you have allowed the highest settings in the software? That is because ISPs throttle many P2P traffic including Limewire/Frostwire. Such traffic policy control has been standardised by the telco industry body 3GPP under the topics of PCRF and PCEF (&lt;a href="http://www.3gpp.org/ftp/Specs/html-info/23203.htm"&gt;TS 23.203&lt;/a&gt;), where &lt;a href="http://en.wikipedia.org/wiki/Deep_packet_inspection"&gt;DPI&lt;/a&gt; is usually used to detect the type of traffic (e.g. Limewire, IMAP, FTP, Skype, etc.) and apply the service policies and charging to the individual traffic sessions. These standards are implemented by all the major network equipment vendors.&lt;/p&gt;  &lt;p&gt;Throttling is applied because such P2P traffic can amount to more than 80% of the total traffic of an ISP, which is not surprising considering a HD movie can be as big as 10s of Gigabytes. So it is pretty certain that even if you have an unlimited internet plan with 1Gbps burst rate, it can still take days/weeks to download that movie you are dying to see.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-104308026339059548?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/104308026339059548/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=104308026339059548' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/104308026339059548'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/104308026339059548'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/05/throttling.html' title='Throttling'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4800941965838748358</id><published>2011-05-08T10:40:00.002+10:00</published><updated>2011-05-17T12:35:45.242+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Hacking'/><title type='text'>Cheating Red Alert 3</title><content type='html'>I recently started to play Red Alert 3 (v1.0) just for the amusement. I am not a serious game player, so naturally I needed to cheap my way through some chapters.   &lt;p&gt;Unlike games such as War Craft 3, which have built-in cheat codes, RA3 has none (or at least I could not find any on the net). Fortunately, there is &lt;a href="http://www.cheatengine.org/aboutce.php"&gt;CheatEngine&lt;/a&gt; – an very handy open source bundle of tools that have been especially built for cheating in games! Since CE has been built by hacker for hackers, documentation is scarce and not easy to find.&lt;/p&gt;&lt;p&gt;From a quick search, there are a couple of ways to cheat in RA3:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Modify money - use the CE’s memory scanner to search for the memory addresses that store the money amount. Once found, modify them to give yourself virtually unlimited money. This approach has been demonstrated on &lt;a href="http://www.youtube.com/watch?v=687cbULbFeY"&gt;YouTube&lt;/a&gt;. The video is blurry, but the method is exactly the same as the first tutorial of CE, which is bundled with the CE installation. &lt;/li&gt;
&lt;li&gt;God Mode, Unlimited Resource and Quick Research – this is done by code injection. The assembly source code is available on the &lt;a href="http://forum.cheatengine.org/viewtopic.php?t=315295"&gt;CE Forum&lt;/a&gt;. However, the forum does not say how to apply the hack. Here, I will show a step by step guide on applying the hack. &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;Once both RA3 and CE are running (doesn’t matter which is first), &lt;/p&gt;&lt;ol&gt;&lt;li&gt;go to CE and select the process named ‘…ra3_1.0.game’ and open it. &lt;/li&gt;
&lt;li&gt;Click the ‘Memory view’ button on CE to open the memory view window. &lt;/li&gt;
&lt;li&gt;From the Memory Viewer window, select the menu &lt;code&gt;Tools –&amp;gt; Auto Assemble (Ctrl-A)&lt;/code&gt;.&amp;#160; This will pop up the &lt;code&gt;Auto assemble&lt;/code&gt; window. &lt;/li&gt;
&lt;li&gt;Paste the assembly code found in the &lt;a href="http://forum.cheatengine.org/viewtopic.php?t=315295"&gt;CE Forum&lt;/a&gt; (also listed below) into the &lt;code&gt;Auto assemble&lt;/code&gt; window. &lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;Execute&lt;/code&gt; button. Click &lt;code&gt;Yes&lt;/code&gt; on the confirmation message to inject the code. You should get another message saying successful. &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;That’s it. When you switch back to RA3, you will find that your money is about 100,000, researches are very fast and the units can take a constant pounding for several minutes without losing health.&lt;/p&gt;&lt;p&gt;The assembly code is shown below. Notice the highlighted lines – to turn the particular cheat off, change the value to 0. Also note that the code for later versions of RA3 is different and they can be found on the CE Forum as well.&lt;/p&gt;&lt;pre class="brush: text; highlight: [153, 155, 157];" name="code"&gt;// Command and Conquer - Red Alert 3 
// Game Version  : 1.0.3174.697 
// Script Version: 1.0 
// CE Version    : 5.4 
// Resource, Research and GodMode 
// 08-Nov-2008 

[ENABLE] 
alloc(MyCode,1024) 

label(_MonResource) 
label(_GodMode) 
label(_MonRPoints) 
label(_MonPlayerID) 
label(_GodM0) 
label(_BackMR) 
label(_BackGM) 
label(_BackMRP) 
label(_BackMPI) 
label(_ExitMR) 
label(_ExitGM) 
label(_ExitMRP) 
label(pResource) 
label(pLastOne) 
label(iPlayerID) 
label(iEnableGM) 
label(iEnableMRP) 
label(iEnableMR) 

registersymbol(MyCode) 
registersymbol(pLastOne) 
registersymbol(iEnableGM) 
registersymbol(iEnableMRP) 
registersymbol(iEnableMR) 
//============================= 
// Hacking Points 
ra3_1.0.game+3e4acc: 
 jmp _MonResource 
 nop 
_BackMR: 

ra3_1.0.game+30d4ae: 
 jmp _GodMode 
_BackGM: 

ra3_1.0.game+4004e5: 
 jmp _MonRPoints 
_BackMRP: 

ra3_1.0.game+58bb45: 
 jmp _MonPlayerID 
 nop 
_BackMPI: 

MyCode: 
//========================================= 
_MonResource: 
 push eax 
 mov eax,[iPlayerID] 
 cmp eax,[ecx+20]           // Player's?... 
 pop eax 
 mov ecx,[ecx+000000e4]     // Original code 
 mov [pResource],ecx        // Save ptr for debugging 
 jne _ExitMR                // ...Jump if false 

 cmp dword ptr [iEnableMR],0 
 je _ExitMR                // Jump if Monitor Resource is disabled 

 mov ecx,[ecx] 

 cmp dword ptr [ecx+04],#100000 
 jge _ExitMR                // Jump if greater then 100000 

 mov dword ptr [ecx+04],#100000 

_ExitMR: 
 mov ecx,[pResource] 
 jmp _BackMR                // back to main code 

//========================================= 
_GodMode: 
 push eax 

 mov eax,[esi-08]           // Get ptr to Unit 
 or eax,eax                 // Null Ptr? 
 jz _ExitGM                 // Jump if true 

 mov eax,[eax+00000418]     // Get ptr to Player 
 mov eax,[eax+20]           // Get ID 

 cmp eax,[iPlayerID]        // Player's?... 
 jne _ExitGM                // Jump if false 

 mov [pLastOne],esi        // Save ptr for debugging 

 mov eax,[esi+30] 
 cmp eax,00000070          // Is it an effect? 
 je _ExitGM                // Jump if true 

_GodM0: 
 cmp dword ptr [iEnableGM],0 
 je _ExitGM                // Jump if God Mode is disabled 

 movss xmm0,[esi+0c]       // Get Maximum HP 

_ExitGM: 
 movss [esi+04],xmm0       // Original code 

 pop eax 
 test eax,eax              // Restore EFLAGS 
 jmp _BackGM               // Back to main code 

//========================================= 
// Quick Research 
_MonRPoints: 
 push edx 

 movss [esi+2c],xmm0       // Original code 

 cmp dword ptr [iEnableMRP],0 
 je _ExitMRP               // Jump if Quick Research is disabled 

 mov edx,[esi+28] 
 mov edx,[edx+20] 
 cmp edx,[iPlayerID]       // Player´s research? 
 jne _ExitMRP              // Jump if false 

 mov edx,43af0000          // 350.0 
 cmp edx,[esi+2c] 
 jle _ExitMRP 

 mov [esi+2c],edx          

_ExitMRP: 
 pop edx 
 jmp _BackMRP              // Back to main code 

//========================================= 
_MonPlayerID: 
 mov eax,[edi+00000080] 
 mov [iPlayerID],eax       // Save Player ID for further use 
 jmp _BackMPI              // Back to main code 

//========================================= 
// Variables 
iPlayerID: 
 dd 0 
pResource: 
 dd 0 
pLastOne: 
 dd 0 
iEnableGM: 
 dd 1 
iEnableMRP: 
 dd 1 
iEnableMR: 
 dd 1 

//========================================= 
// Original Codes 

[DISABLE] 
ra3_1.0.game+3e4acc: 
 mov ecx,[ecx+000000e4] 

ra3_1.0.game+30d4ae: 
 movss [esi+04],xmm0 

ra3_1.0.game+4004e5: 
 movss [esi+2c],xmm0 

ra3_1.0.game+58bb45: 
 mov eax,[edi+00000080] 

unregistersymbol(MyCode) 
unregistersymbol(pLastOne) 
unregistersymbol(iEnableGM) 
unregistersymbol(iEnableMRP) 
unregistersymbol(iEnableMR) 

dealloc(MyCode) &lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4800941965838748358?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4800941965838748358/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4800941965838748358' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4800941965838748358'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4800941965838748358'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/05/cheating-red-alert-3.html' title='Cheating Red Alert 3'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4196132650884565261</id><published>2011-04-18T14:48:00.001+10:00</published><updated>2011-04-18T14:48:09.377+10:00</updated><title type='text'>IE9 and MIME Type Handling</title><content type='html'>&lt;p&gt;I upgraded my Internet Explorer from v8 to v9 a couple of weeks ago. I immediately discovered that the &lt;a href="http://alexgorbatchev.com/SyntaxHighlighter/"&gt;SyntaxHighlighter&lt;/a&gt;I use on my Blog is no longer working. I turned on the console view (by pressing F12) of IE9 and it showed that the Javascript file of SyntaxHighlighter loaded from my Google Site is actually named &lt;code&gt;syntaxhighlighter2.txt&lt;/code&gt;; however, the type of the link was &lt;code&gt;text/javascript&lt;/code&gt;.&lt;/p&gt;  &lt;p&gt;This mismatch between the file name extension and the specified MIME type upsets IE9, so it refuses to load my SyntaxHighlighter script. I tried to turn off the MIME handling feature in IE9 by changing the Windows registry following instructions in the following articles:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms775148(v=vs.85).aspx"&gt;Handling MIME Types in Internet Explorer&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/ms775147(v=vs.85).aspx"&gt;MIME Type Detection in Internet Explorer&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;None worked. &lt;/p&gt;  &lt;p&gt;So I took the simple approach – change my SyntaxHighlighter file extension from &lt;code&gt;.txt&lt;/code&gt; to &lt;code&gt;.js&lt;/code&gt; – and it worked.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4196132650884565261?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4196132650884565261/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4196132650884565261' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4196132650884565261'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4196132650884565261'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/04/ie9-and-mime-type-handling.html' title='IE9 and MIME Type Handling'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4818363446585048365</id><published>2011-03-24T16:49:00.003+11:00</published><updated>2011-05-17T12:36:17.823+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='LinkedIn'/><category scheme='http://www.blogger.com/atom/ns#' term='Hacking'/><title type='text'>Hacking LinkedIn API – Take 2</title><content type='html'>&lt;p&gt;I had taken a long &lt;a href="http://romenlaw.blogspot.com/2011/01/month-without-internet.html"&gt;holiday from work and from internet&lt;/a&gt;. So I hadn’t touched this blog till now. I discovered that many visits to my blog was to one of my post on &lt;a href="http://romenlaw.blogspot.com/2010/01/hacking-linkedin-api.html"&gt;Hacking LinkedIn API&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;When the LinkedIn APIs first came out I played with it and bypassed the convoluted and stupid OAuth manual process. I documented the approach in my &lt;a href="http://romenlaw.blogspot.com/2010/01/hacking-linkedin-api.html"&gt;blog post&lt;/a&gt;. However, soon after I posted it, LinkedIn had changed the login page and the access code page so that the HTML posting and scraping code no longer worked. I promised to update my Java code, so here it is.&lt;/p&gt;&lt;p&gt;This time, I am using a later version of the Java wrapper for the LinkedIn APIs – &lt;a href="http://code.google.com/p/linkedin-j/downloads/list"&gt;LinkedIn-J 1.0.361&lt;/a&gt;. The Java API has totally changed from the previous one I used.&lt;/p&gt;&lt;p&gt;The main difference of my 2nd attempt of hacking the APIs are as following:&lt;/p&gt;&lt;h2&gt;Using HTMLEditorKit&lt;/h2&gt;&lt;p&gt;Once you go to the authorisation URL returned by LinkedIn, it displays a login form (you must clear your browser’s cookie to disable the auto-login). In this login form, there are a number of hidden fields just like before. However, this time LinkedIn had added a few more fields and a dynamic one – named &lt;code&gt;csrfToken&lt;/code&gt;.&amp;#160; When we submit the form, we must include all the hidden field values as well. So we need to parse this HTML string to retrieve the dynamic field values. I used &lt;code&gt;HTMLEditorKit&lt;/code&gt; library because it’s part of Java Swing so no external JARs are required. The login form looks something like this.&lt;/p&gt;&lt;pre class="html" name="code"&gt;&lt;form class="standard-form" method="post" name="oauthAuthorizeForm" action="/uas/oauth/authorize/submit"&gt; ...
 &lt;input id="extra-oauthAuthorizeForm" type="hidden" name="extra" /&gt;
 &lt;input id="access-oauthAuthorizeForm" value="-3" type="hidden" name="access" /&gt;
 &lt;input id="agree-oauthAuthorizeForm" value="true" type="hidden" name="agree" /&gt;&lt;input id="oauth_token-oauthAuthorizeForm" value="6d571231-abcd-65e0-7efe-aedf7c512345" type="hidden" name="oauth_token" /&gt;
 &lt;input id="appId-oauthAuthorizeForm" type="hidden" name="appId" /&gt;
 &lt;input id="csrfToken-oauthAuthorizeForm" value="ajax:1912345156612345469" type="hidden" name="csrfToken" /&gt;
 &lt;input id="sourceAlias-oauthAuthorizeForm" value="0_8L1usXMS_e_-OauthissostupidAXKyuk4aDiAi" type="hidden" name="sourceAlias" /&gt;
&lt;/form&gt;&lt;/pre&gt;&lt;p&gt;So to retrieve the field values, I added a HTML parser callback class.&lt;/p&gt;&lt;pre class="java" name="code"&gt;class ReportAttributes extends HTMLEditorKit.ParserCallback {
 public String csrfToken, sourceAlias;

 public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes, int position) {
  this.listAttributes(attributes);
 }
 public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes, int position) { 
  this.listAttributes(attributes); 
 } 
 private void listAttributes(AttributeSet attributes) {
  if (attributes.containsAttribute(HTML.Attribute.ID, &amp;quot;csrfToken-oauthAuthorizeForm&amp;quot;)) {
   csrfToken=attributes.getAttribute(HTML.Attribute.VALUE).toString();
   System.out.println(&amp;quot;csrfToken=&amp;quot;+csrfToken);
  } else if (attributes.containsAttribute(HTML.Attribute.ID, &amp;quot;sourceAlias-oauthAuthorizeForm&amp;quot;)) {
   sourceAlias=attributes.getAttribute(HTML.Attribute.VALUE).toString();
   System.out.println(&amp;quot;sourceAlias=&amp;quot;+sourceAlias);
  }
 }
}&lt;/pre&gt;&lt;h2&gt;Enabling Cookies&lt;/h2&gt;&lt;p&gt;It turned out that you must enable cookies otherwise LinkedIn will complain when you try to submit the login form. So here is the snippet for enabling cookie.&lt;/p&gt;&lt;pre class="brush: java;" name="code"&gt;CookieManager manager = new CookieManager();
 manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
 CookieHandler.setDefault(manager);&lt;/pre&gt;&lt;p&gt;The overall structure of the code is pretty similar to before. Here is the full source code. Just modify the highlighted lines and it should just work for you.&lt;/p&gt;&lt;pre class="brush: java; highlight: [54, 55, 56, 57];" name="code"&gt;package com.laws.LinkedIn;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.swing.text.AttributeSet;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;

import com.google.code.linkedinapi.client.LinkedInApiClient;
import com.google.code.linkedinapi.client.LinkedInApiClientFactory;
import com.google.code.linkedinapi.client.oauth.LinkedInAccessToken;
import com.google.code.linkedinapi.client.oauth.LinkedInOAuthService;
import com.google.code.linkedinapi.client.oauth.LinkedInOAuthServiceFactory;
import com.google.code.linkedinapi.client.oauth.LinkedInRequestToken;
import com.google.code.linkedinapi.schema.Person;

class ParserGetter extends HTMLEditorKit {
   public HTMLEditorKit.Parser getParser() {
     return super.getParser();
   }
 }
class ReportAttributes extends HTMLEditorKit.ParserCallback {
 public String csrfToken, sourceAlias;

  public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes, int position) {
   this.listAttributes(attributes);
  }
  public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes, int position) { 
   this.listAttributes(attributes); 
  } 
  private void listAttributes(AttributeSet attributes) {
   if (attributes.containsAttribute(HTML.Attribute.ID, &amp;quot;csrfToken-oauthAuthorizeForm&amp;quot;)) {
  csrfToken=attributes.getAttribute(HTML.Attribute.VALUE).toString();
  System.out.println(&amp;quot;csrfToken=&amp;quot;+csrfToken);
  } else if (attributes.containsAttribute(HTML.Attribute.ID, &amp;quot;sourceAlias-oauthAuthorizeForm&amp;quot;)) {
  sourceAlias=attributes.getAttribute(HTML.Attribute.VALUE).toString();
  System.out.println(&amp;quot;sourceAlias=&amp;quot;+sourceAlias);
   }
  }
}


public class Main {
 static final String apiKey=&amp;quot;your api key&amp;quot;;
 static final String secretKey=&amp;quot;your secret key&amp;quot;;
 static final String login=&amp;quot;name%40company.com&amp;quot;;
 static final String password=&amp;quot;password&amp;quot;;
 
 static public String getPin(String authUrl, String token) {
  DataOutputStream dataOut;
  ParserGetter kit = new ParserGetter();
     HTMLEditorKit.Parser parser = kit.getParser();
     ReportAttributes callback = new ReportAttributes();
     // must enable cookie, otherwise LinkedIn will not give you the access code
     CookieManager manager = new CookieManager();
     manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
     CookieHandler.setDefault(manager);


        try {
         // this section gets the LinkedIn login form.
            URL url = new URL(authUrl);
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod(&amp;quot;POST&amp;quot;);
            con.setUseCaches(false);
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setRequestProperty(&amp;quot;Content-Type&amp;quot;, &amp;quot;application/x-www-form-urlencoded&amp;quot;);
            //SSLException thrown here if server certificate is invalid
            InputStreamReader reader = new InputStreamReader(con.getInputStream());
            parser.parse(reader, callback, true);
            System.out.println(&amp;quot;-------------------------------------&amp;quot;);
            
            // POST the login form and get the access/verification code.
            url = new URL(&amp;quot;https://www.linkedin.com/uas/oauth/authorize/submit&amp;quot;);
            con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod(&amp;quot;POST&amp;quot;);
            con.setUseCaches(false);
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setRequestProperty(&amp;quot;User-Agent&amp;quot;, &amp;quot;Mozilla/4.0&amp;quot;);
            con.setRequestProperty(&amp;quot;Content-Type&amp;quot;, &amp;quot;application/x-www-form-urlencoded&amp;quot;);
            
            dataOut = new DataOutputStream(con.getOutputStream());

            String s=&amp;quot;session_login=&amp;quot;+login
             +&amp;quot;&amp;amp;session_password=&amp;quot; + password
             + &amp;quot;&amp;amp;duration=0&amp;amp;authorize=Ok%2C%20I'll%20Allow%20It&amp;amp;extra=&amp;amp;access=-3&amp;amp;agree=true&amp;amp;oauth_token=&amp;quot;
          +token+&amp;quot;&amp;amp;appId=&amp;amp;csrfToken=&amp;quot;+callback.csrfToken+&amp;quot;&amp;amp;sourceAlias=&amp;quot;+callback.sourceAlias;
            System.out.println(&amp;quot;writing bytes: &amp;quot;+s);
            dataOut.writeBytes(s);
            dataOut.flush();
            dataOut.close();

            //SSLException thrown here if server certificate is invalid
            String returnedHtml=convertStreamToString(con.getInputStream());
            //System.out.println(returnedHtml);
            
            /* extract the pin from the html string. the block looks like this       
          &lt;div class="access-code"&gt;73336&lt;/div&gt;* It turns out that the whole html string only contains one 'div with class=&amp;quot;access-code&amp;quot;'
             * also it seems that the pin is always 5-digit long,
             * so we will just crudely detect that string and get the pin out.
             * A proper HTML parser should be used in a real application.  
             */
            int i=returnedHtml.indexOf(&amp;quot;access-code\&amp;quot;&amp;gt;&amp;quot;);
            String pin = returnedHtml.substring(i+13, i+13+5);
            System.out.println(&amp;quot;pin=&amp;quot;+pin);
            return pin;         
        } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   return null;
  }
    }

 /**
  * @param args
  */
 public static void main(String[] args) {
  final LinkedInOAuthService oauthService = LinkedInOAuthServiceFactory.getInstance().createLinkedInOAuthService(
    apiKey, secretKey);
  LinkedInRequestToken requestToken = oauthService.getOAuthRequestToken();
  
        System.out.println(&amp;quot;request token: &amp;quot;);
        System.out.println(&amp;quot;  auth URL: &amp;quot;+requestToken.getAuthorizationUrl());
        System.out.println(&amp;quot;  token: &amp;quot;+requestToken.getToken());
        System.out.println(&amp;quot;  token secret: &amp;quot;+requestToken.getTokenSecret());
        System.out.println(&amp;quot;  expiration time: &amp;quot;+requestToken.getExpirationTime());
        
        // get the access code
        String pin=getPin(requestToken.getAuthorizationUrl(), requestToken.getToken());
        
        LinkedInAccessToken accessToken = oauthService.getOAuthAccessToken(requestToken, pin);
        final LinkedInApiClientFactory factory = LinkedInApiClientFactory.newInstance(apiKey, secretKey); 
        final LinkedInApiClient client = factory.createLinkedInApiClient(accessToken);  
        
        // now we can call the LinkedIn APIs.
        Person profile = client.getProfileForCurrentUser();
        System.out.println(&amp;quot;I am &amp;quot;+profile.getFirstName()+&amp;quot; &amp;quot;+profile.getLastName());
 }
 // Stolen liberally from http://www.kodejava.org/examples/266.html
    public static String convertStreamToString(InputStream is) {
        /*
         * To convert the InputStream to String we use the BufferedReader.readLine()
         * method. We iterate until the BufferedReader return null which means
         * there's no more data to read. Each line will appended to a StringBuilder
         * and returned as String.
         */
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + &amp;quot;\n&amp;quot;);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return sb.toString();
    }

}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4818363446585048365?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4818363446585048365/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4818363446585048365' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4818363446585048365'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4818363446585048365'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/03/hacking-linkedin-api-take-2.html' title='Hacking LinkedIn API – Take 2'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-950463984169159944</id><published>2011-01-09T22:30:00.001+11:00</published><updated>2011-01-09T22:30:12.078+11:00</updated><title type='text'>Month without Internet</title><content type='html'>&lt;p&gt;I had a wonderful month-long holiday in the Philippines. In the province (as they call the countryside in Pinoy lingo) home, I did not have any fixed line phone service, hence no wired (ADSL or cable) internet access. So I purchased wireless broadband USB dongles from both Smart and Globe (both made by Huawei). They claim to be 3G broadband. But there were no 3G coverage from Smart at where I lived; and there was 3G signal from Globe – sometimes 3G, sometimes HSPDA and sometimes just GPRS. Even with 3G coverage, the data speeds were pathetic – the overall speed was worse than dial up modems in the old days – definitely narrowband (speeds ranging from 0 to about 40kbps most of the time).&lt;/p&gt;  &lt;p&gt;So instead of feeling frustrated with Philippines telcos (as I usually do), I decided to stay away from the internet altogether. Without internet and piano, I found that I had so much time and got to do what I had always wanted to do so badly for so long. I managed to read a Cisco CCNA book and practiced the labs using &lt;a href="http://www.cisco.com/web/learning/netacad/course_catalog/PacketTracer.html"&gt;Cisco Packet Tracer&lt;/a&gt; and &lt;a href="http://www.gns3.net/"&gt;GNS3&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;More rewardingly, I managed to start doing water colour paintings. I had always wanted to learn and practise water color for many years – I bought several books, some cheap brushes, colors and a pallet over the years. But I never had a chance to start painting until then. Once I started, I did not want to stop. I was painting at home, on tour, on other islands. I ended up with over a dozen nature and still life &lt;a href="http://picasaweb.google.com/romen.law/WaterColorInPhilippines#"&gt;paintings&lt;/a&gt;. It was the first time that I used the proper gear – water color papers, brushes (they are absolute bargains from National Book Store in Tacloban – the same brushes would cost 10 times more in Sydney). Now that I am back in Sydney, I will keep painting and keep the online time to a minimum.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-950463984169159944?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/950463984169159944/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=950463984169159944' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/950463984169159944'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/950463984169159944'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2011/01/month-without-internet.html' title='Month without Internet'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7491160290763940078</id><published>2010-11-19T10:31:00.001+11:00</published><updated>2010-11-19T10:31:29.179+11:00</updated><title type='text'>SN 1979C</title><content type='html'>I have been travelling for the last week. Everywhere I went there were news about the youngest black hole ever found. I could not understand how people kept saying the black hole (if it is one) is 30 years old yet it's 50 million light years away. I found the answer in the NASA's &lt;a href="http://chandra.harvard.edu/photo/2010/sn1979c/"&gt;CHANDRA X-Ray Observatory&lt;/a&gt; site where the original news was released.   &lt;p&gt;The 30-year number is the &lt;em&gt;observed value&lt;/em&gt; meaning that the actual age of the object is well over that and we will not know its current status until 50 million years later. It also means that the Earth has been safe from it for the last 50 million years and there is no need for a panic attack.&lt;/p&gt;  &lt;p&gt;What the article does not reveal is in what state the object was when it was first observed in 1979 – whether it had already &lt;a href="http://www.physlink.com/education/askexperts/ae253.cfm"&gt;turned into a supernova / black hole&lt;/a&gt; or still a star. &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7491160290763940078?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7491160290763940078/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7491160290763940078' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7491160290763940078'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7491160290763940078'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/11/sn-1979c.html' title='SN 1979C'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8549461769021947864</id><published>2010-09-26T09:32:00.002+10:00</published><updated>2010-09-26T19:40:42.633+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><title type='text'>JAAS with Tomcat</title><content type='html'>&lt;p&gt;As JAAS has been included as part of JRE core since Java 1.4, it should be quite prevalent by now. However, I cannot seem to find many online resources that detail the&amp;#160; steps involved to set up JAAS with Tomcat. Here, I outline these steps from a beginner’s perspective.&lt;/p&gt;  &lt;h2&gt;Step 1 – Implement the JASS interfaces.&lt;/h2&gt;  &lt;p&gt;The basic interfaces to be implemented are &lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;code&gt;Principal&lt;/code&gt; – there should be two implementations of this interface: One representing the user; another one (or more) represent the roles. The important method of this interface is &lt;code&gt;getName()&lt;/code&gt; which returns the name of the user or role that it represents. &lt;/li&gt;    &lt;li&gt;&lt;code&gt;LoginModule&lt;/code&gt; – this is the main class that usually serves as the wrapper for customised authentication logic. The &lt;code&gt;login()&lt;/code&gt; method is where the customised login logic should be invoked. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Once implemented, JAR up these classes including the customised login classes and put it in &lt;code&gt;$CATALINA_BASE/lib&lt;/code&gt;.&lt;/p&gt;  &lt;p&gt;See the code segments at the end for example implementations.&lt;/p&gt;  &lt;h2&gt;Step 2 – Configure Tomcat Server&lt;/h2&gt;  &lt;p&gt;Edit &lt;code&gt;$CATALINA_BASE/conf/server.xml&lt;/code&gt; to put in the JAASRealm configuration. As instructed by Tomcat’s &lt;a href="http://tomcat.apache.org/tomcat-7.0-doc/realm-howto.html#Configuring_a_Realm"&gt;Realm-HOW-TO&lt;/a&gt; documentation, this can be done at various levels - &lt;code&gt;&amp;lt;Engine&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;Host&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;Context&amp;gt;&lt;/code&gt;. I added it inside the &lt;code&gt;&amp;lt;Host&amp;gt;&lt;/code&gt; tag:&lt;/p&gt;  &lt;pre class="xml" name="code"&gt;
&lt;Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"&gt;

	&lt;Realm className="org.apache.catalina.realm.JAASRealm" 
		appName="acczk" userClassNames="com.laws.acc.jaas.AccPrincipal" 
		roleClassNames="com.laws.acc.jaas.AccRole"&gt;
	&lt;/Realm&gt;
...
&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;userClassNames&lt;/code&gt; and &lt;code&gt;roleClassNames&lt;/code&gt; are the classes that were created in step 1.&lt;/p&gt;
&lt;h2&gt;Step 3 – The JRE login configuration&lt;/h2&gt;
Create a &lt;a href="http://download.oracle.com/javase/1.4.2/docs/guide/security/jaas/tutorials/LoginConfigFile.html"&gt;JAAS Login Configuration&lt;/a&gt; file. I called it &lt;code&gt;jaas.config&lt;/code&gt; and put it under &lt;code&gt;$CATALINA_BASE/conf&lt;/code&gt;:
&lt;pre class="java" name="code"&gt;
acczk {
  com.laws.acc.jaas.AccLoginModule required;
};
&lt;/pre&gt;
&lt;p&gt;There are &lt;a href="http://download.oracle.com/javase/1.4.2/docs/guide/security/jaas/tutorials/LoginConfigFile.html"&gt;two ways to configure the JRE&lt;/a&gt; to load the login ocnfiguration file. I prefer to specify it in &lt;code&gt;JAVA_OPTS&lt;/code&gt;. For Tomcat, this means to create a &lt;code&gt;setenv.sh&lt;/code&gt; or &lt;code&gt;setenv.bat&lt;/code&gt; in the &lt;code&gt;$CATALINA_BASE/bin&lt;/code&gt; directory. Here is my &lt;code&gt;setenv.bat&lt;/code&gt;:&lt;/p&gt;
&lt;pre name="code" class="shell"&gt;
set JAVA_OPTS=-Djava.security.auth.login.config==C:/Tools/apache-tomcat-7.0.2/conf/jaas.config
&lt;/pre&gt;

&lt;h2&gt;Step 4 – The Login Form&lt;/h2&gt;

&lt;p&gt;The login method can be BASIC or FORM-based. I prefer FORM. A vanilla HTML form should do. I happen to be using ZK so my HTML login form is in the file &lt;code&gt;login.zul&lt;/code&gt;: &lt;/p&gt;
&lt;pre class="xml" name="code"&gt;
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c" ?&gt;

&lt;zk&gt;
&lt;image src="img/logo.png"/&gt;
Welcome to ${c:l('client_name')}!
&lt;separator spacing="10px"/&gt;
&lt;div align="center" width="100%"&gt;
&lt;window xmlns:h="http://www.w3.org/1999/xhtml" title="ACC Login Form" 
width="320px" position="cetner,center" border="normal"&gt;     
	&lt;zscript&gt; 
	    import com.laws.acc.ws.*;
	    
		String errMsg = null; 
		
		LoginResponse loginResponse=AccSoapClientProxy.getLoginResponse();
		if(loginResponse!=null) {
			switch(loginResponse.getResultCode()) {
			case ResultCode.NOT_LOGGED_IN:
				errMsg="Not logged in.";
				break;
			case ResultCode.CONNECTION_ERROR:
				errMsg="Cannot login to server:\n"+loginResponse.getErrorDescription();
				break;
			default:
				errMsg="Login failure:\n"+loginResponse.getErrorDescription();
			}
		}
	&lt;/zscript&gt;     
	&lt;h:form method="post" id="j_security_check" action="j_security_check"&gt;         
	&lt;grid&gt;        
		&lt;columns&gt;
			&lt;column width="100px" align="left"&gt;&lt;/column&gt;
			&lt;column align="left"&gt;&lt;/column&gt;
		&lt;/columns&gt;     
		&lt;rows&gt;                 
			&lt;row&gt;                     
				Username :                     
				&lt;textbox id="j_username" name="j_username" value="${sessionScope.j_username}" width="200px" /&gt;                 
			&lt;/row&gt;                 
			&lt;row&gt;                     
				Password :                     
				&lt;textbox id="j_password" name="j_password" type="password" width="200px" /&gt;                 
			&lt;/row&gt;             
		&lt;/rows&gt;         
	&lt;/grid&gt;         
	&lt;div width="100%" align="right"&gt;    
		&lt;separator height="5px"&gt;&lt;/separator&gt;         
		&lt;h:input type="submit" value="Login" /&gt;         
	&lt;/div&gt;     
	&lt;/h:form&gt;     
	&lt;div if="${errMsg != null}" width="100%" style="color:red" align="left"&gt;${errMsg}&lt;/div&gt;     
	&lt;zscript&gt;         
		j_username.focus();     
	&lt;/zscript&gt; 
&lt;/window&gt;
&lt;/div&gt;
&lt;/zk&gt;
&lt;/pre&gt;
The important part here is the HTML form employing &lt;code&gt;j_username&lt;/code&gt;, &lt;code&gt;j_password&lt;/code&gt; and &lt;code&gt;j_security_check&lt;/code&gt;.
&lt;h2&gt;Step 5 - Configure web.xml&lt;/h2&gt;
This is standard configuration to enable security constraints.
&lt;pre name="code" class="xml"&gt;
&lt;security-constraint&gt;
      &lt;display-name&gt;ACC Security Constraint&lt;/display-name&gt;
      &lt;web-resource-collection&gt;
         &lt;web-resource-name&gt;Protected Area&lt;/web-resource-name&gt;
         &lt;!-- Define the context-relative URL(s) to be protected --&gt;
         &lt;url-pattern&gt;/index.zul&lt;/url-pattern&gt;
	 &lt;url-pattern&gt;/secure/*&lt;/url-pattern&gt;
         &lt;!-- If you list http methods, only those methods are protected --&gt;
         &lt;http-method&gt;DELETE&lt;/http-method&gt;
         &lt;http-method&gt;GET&lt;/http-method&gt;
         &lt;http-method&gt;POST&lt;/http-method&gt;
         &lt;http-method&gt;PUT&lt;/http-method&gt;
      &lt;/web-resource-collection&gt;
      &lt;auth-constraint&gt;
         &lt;!-- Anyone with one of the listed roles may access this area --&gt;
         &lt;role-name&gt;Acc User&lt;/role-name&gt;
      &lt;/auth-constraint&gt;
    &lt;/security-constraint&gt;
	&lt;security-role&gt;
	  &lt;description&gt;Generic authenticated ACC user&lt;/description&gt;
	  &lt;role-name&gt;Acc User&lt;/role-name&gt;
	&lt;/security-role&gt;
    &lt;!-- Default login configuration uses form-based authentication --&gt;
    &lt;login-config&gt;
      &lt;auth-method&gt;FORM&lt;/auth-method&gt;
      &lt;realm-name&gt;Form-Based Authentication Area&lt;/realm-name&gt;
      &lt;form-login-config&gt;
        &lt;form-login-page&gt;/login.zul&lt;/form-login-page&gt;
        &lt;form-error-page&gt;/login.zul&lt;/form-error-page&gt;
      &lt;/form-login-config&gt;
    &lt;/login-config&gt;
  &lt;session-config&gt;	
  	&lt;session-timeout&gt;120&lt;/session-timeout&gt;	
  &lt;/session-config&gt;
&lt;/pre&gt;
&lt;p&gt;My JAAS implementation files:&lt;/p&gt;
&lt;code&gt;AccLoginModule.java&lt;/code&gt;:
&lt;pre name="code" class="java"&gt;
package com.laws.acc.jaas;

import java.io.IOException;
import java.util.Map;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import com.laws.acc.ws.AccSoapClientProxy;
import com.laws.acc.ws.LoginResponse;

public class AccLoginModule implements LoginModule {
	// initial state
    private Subject subject;
    private CallbackHandler callbackHandler;
    private Map&lt;String, ?&gt; sharedState;
    private Map&lt;String, ?&gt; options;
    
    private AccPrincipal principal;
    private boolean committed = false;

	@Override
	public boolean abort() throws LoginException {
		if(!committed)
			return false;
		if(principal!=null) {
			logout();
			principal=null;
		}
		return true;
	}

	@Override
	public boolean commit() throws LoginException {
		if (principal == null){
            return false;
        }
        if (!subject.getPrincipals().contains(principal))
            subject.getPrincipals().add(principal);
	// hardcoded role assignment.
        AccRole role=new AccRole();
        if(!subject.getPrincipals().contains(role))
        	subject.getPrincipals().add(role);
        committed = true;
        return (true);
	}

	@Override
	public void initialize(Subject subject, CallbackHandler callbackHandler,
			Map&lt;String, ?&gt; sharedState, Map&lt;String, ?&gt; options) {
		this.subject = subject;
		this.callbackHandler = callbackHandler;
		this.sharedState = sharedState;
		this.options = options;
	}

	@Override
	public boolean login() throws LoginException {
		if (callbackHandler == null)
            throw new LoginException("No CallbackHandler specified");
        Callback callbacks[] = new Callback[2];
        callbacks[0] = new NameCallback("Username: ");
        callbacks[1] = new PasswordCallback("Password: ", false);
 
        // Interact with the user to retrieve the username and password
        String username = null;
        String password = null;
        try {
            callbackHandler.handle(callbacks);
            username = ((NameCallback) callbacks[0]).getName();
            password = new String(((PasswordCallback) callbacks[1]).getPassword());
            LoginResponse response = AccSoapClientProxy.login(username,password);
            if (response.getResultCode()!=0)
                return false;
            principal  = new AccPrincipal(response);
            return true;
        } catch (IOException e) {
            throw new LoginException(e.toString());
        } catch (UnsupportedCallbackException e) {
            throw new LoginException(e.toString());
        }
	}

	@Override
	public boolean logout() throws LoginException {
		committed=false;
		subject.getPrincipals().remove(principal);

		return false;
	}

}
&lt;/pre&gt;
&lt;code&gt;AccPrincipal.java&lt;/code&gt;:
&lt;pre name="code" class="java"&gt;
package com.laws.acc.jaas;

import java.io.Serializable;
import java.security.Principal;
import com.laws.acc.ws.*;

public class AccPrincipal implements Principal, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 5002820876845306935L;
	private LoginResponse loginResponse=null;
	
	public AccPrincipal(LoginResponse lr) {
		this.loginResponse=lr;
	}
	
	@Override
	public String getName() {
		return loginResponse.getUserName();
	}

	@Override
	public boolean equals(Object other) {
		if(other==null) return false;
		if(other==this) return true;
		if(other instanceof AccPrincipal) {
			AccPrincipal that=(AccPrincipal) other;
			return loginResponse.getUserId()== that.getLoginResponse().getUserId();
		} else
			return false;
	}
	
	public LoginResponse getLoginResponse() {
		return loginResponse;
	}

	public void setLoginResponse(LoginResponse loginResponse) {
		this.loginResponse = loginResponse;
	}

	@Override
	public int hashCode() {
		return loginResponse.getUserId();
	}
}
&lt;/pre&gt;
&lt;code&gt;AccRole.java&lt;/code&gt;:
&lt;pre name="code" class="java"&gt;
package com.laws.acc.jaas;

import java.security.Principal;

public class AccRole implements Principal {

	@Override
	public String getName() {
		return "Acc User";
	}
	
	@Override
	public boolean equals(Object other) {
		if (other==null) return false;
		if(other==this) return true;
		if(other instanceof AccRole) {
			return this.getName().equals(((AccRole) other).getName());
		} else
			return false;
	}

	@Override
	public int hashCode() {
		return getName().hashCode();
	}
}
&lt;/pre&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8549461769021947864?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8549461769021947864/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8549461769021947864' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8549461769021947864'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8549461769021947864'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/09/jaas-with-tomcat.html' title='JAAS with Tomcat'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8885048170941960131</id><published>2010-09-21T12:02:00.002+10:00</published><updated>2010-09-21T12:09:33.799+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><title type='text'>Java Logging &amp; Properties File</title><content type='html'>&lt;p&gt;I prefer to have the &lt;a href="http://en.wikipedia.org/wiki/.properties"&gt;properties&lt;/a&gt; file at the application level or JAR level if I am writing a library, with as many configuration entries in the same file as logically possible so that I end up with as few files as possible. So my properties file is at the root level of the JAR file.&lt;/p&gt;  &lt;p&gt;To load the &lt;code&gt;config.properties&lt;/code&gt; file at the root of the JAR:&lt;/p&gt;  &lt;pre class="java" name="code"&gt;// ResourceBundle expectes the file extension to be .properties by default.
ResourceBundle bundle = ResourceBundle.getBundle(&amp;quot;config&amp;quot;,
    Locale.getDefault(),
    Thread.currentThread().getContextClassLoader());
String dmsAddress = bundle.getString(&amp;quot;dmsAddress&amp;quot;);&lt;/pre&gt;
Similarly when I use Java Logging I put the &lt;code&gt;logging.properties&lt;/code&gt; file at the same location and load it upon application start up using &lt;code&gt;&lt;a href="http://download.oracle.com/javase/1.4.2/docs/api/java/util/logging/LogManager.html"&gt;LogManager&lt;/a&gt;&lt;/code&gt; so that I don't have to tamper with the properties file at &lt;code&gt;JRE/lib&lt;/code&gt;. 

&lt;p&gt;The &lt;code&gt;logging.properties&lt;code&gt; file:&lt;/code&gt; &lt;/code&gt;&lt;/p&gt;

&lt;pre class="java" name="code"&gt;handlers = java.util.logging.ConsoleHandler
.level=INFO

java.util.logging.ConsoleHandler.level = INFO
com.laws.acc.level = ALL
com.laws.acc.ws.level = ALL&lt;/pre&gt;
It is better to copy the JRE's &lt;code&gt;logging.properties&lt;/code&gt; file and modify it. To load the logging configuration: 

&lt;pre class="java" name="code"&gt;LogManager logMan=LogManager.getLogManager();
logMan.readConfiguration(Thread.currentThread().getClass().getResourceAsStream(&amp;quot;/logging.properties&amp;quot;));&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Resources&lt;/strong&gt;: &lt;em&gt;&lt;a href="http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html?page=2"&gt;Smartly Load Your Properties&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8885048170941960131?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8885048170941960131/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8885048170941960131' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8885048170941960131'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8885048170941960131'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/09/java-logging-properties-file.html' title='Java Logging &amp;amp; Properties File'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1193134992351219752</id><published>2010-08-16T12:26:00.001+10:00</published><updated>2010-08-16T12:26:20.760+10:00</updated><title type='text'>NBN Australia</title><content type='html'>&lt;p&gt;As the federal election campaign comes to its final week, the issue of national broadband network (NBN) has become one of the most important policy differentiators between Labor and the Coalition. &lt;/p&gt;  &lt;p&gt;I have not been paying much attention to NBN until a couple of months ago when a friend of mine was trying to search for jobs in NBN yet found very few. As the government claims that &lt;a href="http://www.itwire.com/it-policy-news/government-tech-policy/41115-labor-now-promises-1gb-nbn-gillard"&gt;NBN will directly employs tens of thousands of people&lt;/a&gt;, you’d expect many job ads from them. But that’s not the case. I did a search on one of the job sites back then and only found a handful.&lt;/p&gt;  &lt;p&gt;I posted a question on &lt;a href="http://www.linkedin.com/groupAnswers?viewQuestionAndAnswers=&amp;amp;discussionID=26992233&amp;amp;gid=107152"&gt;what real benefits average Australians will expect from the NBN&lt;/a&gt; on the NBN Australia discussion group. There has been no answers so far. As I live in a big city and there are many broadband options to choose from – FTTH, cable/HFC, xDSL, 3G or non-standard wireless ones (unWired) offered by many communications service providers (CSPs). I don’t see the need of spending my hard-earned tax dollars trying to improve on something that I cannot experience.&lt;/p&gt;  &lt;p&gt;What I do want to see is to spend my hard-earned tax dollars on those who really need it – regional Australia. There is a big gap between big cities and regional area in terms of infrastructure. Some people have to drive for hours just to go to a hospital – that’s if they have a car. For those who don’t, they will have to rely on community buses, which takes even longer.&lt;/p&gt;  &lt;p&gt;Dick Smith has raised the issue about population and sustainability in Australia. It was hotly debated on &lt;a href="http://www2b.abc.net.au/tmb/Client/Message.aspx?b=114&amp;amp;m=105862&amp;amp;ps=20&amp;amp;dm=2"&gt;Q&amp;amp;A&lt;/a&gt;. One of the issues was population distribution and the infrastructure to support the population. There is no doubt that regional towns want to attract more population and therefore economic growth. You cannot achieve this without improving the infrastructure. So I believe the government should spend less money overall, and focus on narrowing the gap, which will provide real long-term benefits to the Australian people. &lt;/p&gt;  &lt;p&gt;Last week, NBN triumphantly announced that the speed of the fibre network can go to 1Gbps – “&lt;a href="http://www.themercury.com.au/article/2010/08/12/165435_todays-news.html"&gt;10 times what they originally envisaged&lt;/a&gt;”. At least that is what most of the TV news reports have told you. To non-tech heads like Tony Abbot, that might sound astounding. But when you go into the details, it’s not that exciting but could be puzzling. Apparently &lt;a href="http://www.linkedin.com/groupAnswers?viewQuestionAndAnswers=&amp;amp;discussionID=27086352&amp;amp;gid=107152&amp;amp;commentID=21114015&amp;amp;goback=%2Enmp_*1_*1_*1&amp;amp;trk=NUS_DIG_DISC_Q-ucg_mr#commentID_21114015"&gt;the 1Gbps is burst rate, not committed rate&lt;/a&gt; (note that the committed rate is what users really experience). Does that mean NBN and the government were originally envisaging 100Mbps burst rate on a FTTH that costs 46 billion dollars, which is downright laughable?&lt;/p&gt;  &lt;p&gt;Communication networks are hierarchical and each link of the transmission network matters (by the way, can NBN tell us what the bandwidth between Tasmania and mainland is/are?). The overall user experience also depends on all the links from point A to B. The network speed we experience is equal to the weakest link – see &lt;a href="http://www.theaustralian.com.au/national-affairs/ultra-fast-broadband-will-be-slow-on-overseas-links/story-fn59niix-1225904654804?referrer=email&amp;amp;source=AIT_email_nl&amp;amp;emcmp=Ping&amp;amp;emchn=Newsletter&amp;amp;emlist=Member&amp;amp;goback=%2Enmp_*1_*1_*1%2Egde_107152_member_27086352"&gt;Ultra-fast broadband will be slow on overseas links&lt;/a&gt;. Just a few days ago, I downloaded VMWare image of Mac OS X Leopard (2GB) using bit torrent. It took less than 30 minutes. One day later I tried to download &lt;a href="http://www.zorin-os.webs.com/"&gt;Zorin OS&lt;/a&gt; – a Ubuntu Linux distro (1GB) using FTP, it took over 8 hours at which point the connection to the website was broken. So I had to redo it the next day using a mirror site which still took a couple of hours, but it was worth it. The OS is awesome!&lt;/p&gt;  &lt;p&gt;Also, in a wired home environment (or should I say wireless), people typically use WiFi and laptops. With the latest WiFi standard &lt;a href="http://en.wikipedia.org/wiki/Comparison_of_wireless_data_standards"&gt;(802.11n), you can expect 40Mbps (and 200Mbps burst)&lt;/a&gt;. So again, there is a very weak link right at home.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1193134992351219752?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1193134992351219752/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1193134992351219752' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1193134992351219752'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1193134992351219752'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/08/nbn-australia.html' title='NBN Australia'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-2236555568696431792</id><published>2010-08-06T09:46:00.001+10:00</published><updated>2010-08-06T09:53:46.456+10:00</updated><title type='text'>Google WAVES Good-Bye</title><content type='html'>&lt;p&gt;&lt;img title="Google promised to change communications with Wave, but instead the service has reached an untimely death." alt="Google promised to change communications with Wave, but instead the service has reached an untimely death." src="http://images.pcworld.com/news/graphics/173494-google_wave_logo_188.jpg" /&gt;I was shocked when I saw the above heading on one of the news items yesterday. I remember I saw an online video of a demo of Wave mid last year. I was very curious and anxious to try it. However, I could not find out how to join! It was apparently by invitation only. Then I forgot about it – until yesterday. So that online video turned out to be my first and last encounter with the great Wave.&lt;/p&gt;  &lt;p&gt;Someone sited &lt;a href="http://www.eweek.com/c/a/Cloud-Computing/Google-Waves-Failure-10-Reasons-Why-538884/"&gt;top 11 reasons why Wave failed&lt;/a&gt;. I believe the top reason for me is the missing marketing of the product. Every now and then I would receive promotional emails on my Gmail account about Google Ads. But there has been nothing about Wave. I have resisted the temptation to put it on my blog thus far – mainly because I am too lazy and the traffic to my blog is very low (maybe 60 per day and half of them are from crawlers). &lt;/p&gt;  &lt;p&gt;If you take a closer look to how Google works internally, it does not seem so surprising. It is part of Google’s corporate policy to have their employees spend up to 20% of their time to work on any thing that they think interesting. If the management thinks the project has potential, the company will resource and fund it. No doubt Wave was born out of such conditions. So it’s created by nerds for nerds. The Google management probably hoped that it would one day take on the likes of Facebook. Being a nerd I don’t mind the complexity and learning curve – in fact, that’s what draws me to it. But by definition, nerds are not great social networkers – e.g. I joined Facebook and Twitter to test their APIs. That definitely reduced the appeal of Wave. &lt;/p&gt;  &lt;p&gt;Also, the near zero marketing of Wave is no surprise considering how Google makes money – through advertising. That is why they advertise Google Ads and leave the other pet projects live and die on their own. From the days Larry and Sergey started Google, they had been focusing on technical superiority rather than monetisation. That had worked on the search engine since it was so far ahead than the others and the user experience is extremely simple. The contrary is true for Wave. So to let Wave catch up with its competitors purely based on its own merit without any marketing will require some time – much longer than the 1 year that Google had given it so far. Even the search engine did not catch on on day one – Larry and Sergey spent their doctorate years in uni to develop the product.&lt;/p&gt;  &lt;p&gt;Now that the Wave is flushed down the toilet, I wonder what the Google people in Sydney are doing these days.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-2236555568696431792?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/2236555568696431792/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=2236555568696431792' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2236555568696431792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2236555568696431792'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/08/google-waves-good-bye.html' title='Google WAVES Good-Bye'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7903484898431369984</id><published>2010-07-25T16:14:00.001+10:00</published><updated>2010-07-25T16:15:58.183+10:00</updated><title type='text'>Virtual Machines</title><content type='html'>&lt;p&gt;I recently beefed up my desktop machine at home by upgrading to 4GB RAM, 1TB WD Green Caviar hard disk and nVidia GeForce 7600 series. I also installed Windows 7 Ultimate 64-bit on my AMD Athlon 3500+. &lt;/p&gt;  &lt;p&gt;One reason I upgraded my 6-year-old machine is that I want to run virtual machines and try various cloud computing and data grid frameworks.&lt;/p&gt;  &lt;p&gt;As soon as I delved into virtual machine technologies, I found that my AMD CPU which has Socket 939, does not support hardware virtualisation. The &lt;a href="http://en.wikipedia.org/wiki/List_of_AMD_Athlon_64_microprocessors"&gt;list of AMD Athlon 64 CPUs&lt;/a&gt; show that AMD-V only started from Socket AM2. So that basically rules out the possibility of installing 64-bit guest OS on any of the VM software.&lt;/p&gt;  &lt;p&gt;The VM s/w I tried are Oracle/Sun VM &lt;a href="http://www.virtualbox.org/"&gt;VirtualBox&lt;/a&gt; 3.2.6, &lt;a href="http://www.vmware.com/products/player/"&gt;VMWare Player&lt;/a&gt; 3.1.0 and &lt;a href="http://www.vmware.com/products/server/"&gt;VMWare Server&lt;/a&gt; 2.0. My understanding is that they are all free.&lt;/p&gt;  &lt;p&gt;I first installed Ubuntu 10.04 on VirtualBox. The installation was very smooth. All the devices are installed without problem – sound, network, disk drives, USB devices etc.&lt;/p&gt;  &lt;p&gt;Before, VMWare Player was only designed to play a guest OS image. But since version 3, you can also create VM using the player. The experience with VMWare Player is not much different from VirtualBox. The result was equally good. I felt that the VM seems a little faster in VMWare. Also, VMWare Player allows you to connect and disconnect devices on the fly, such as network adapter, printer, sound card, disk drives, USB devices. This is something more flexible than VirtualBox.&lt;/p&gt;  &lt;p&gt;In fact, before I installed VMWare Player, I also tried VMWare Server (you cannot install both on the same host machine). The idea of VMWare Server is quite good – it runs as a services (Windows services, or Unix daemon) so that user can connect to it using web browser. All the actions are done on the browser – the VM console is installed as a browser plug-in. However, my user experience was not so good with VMWare Server. I installed the same Ubuntu iso image on it successfully, but it could not connect to internet (although there is connectivity between the host machine and the guest VM), and sound card was not detected. I could not add these h/w from outside the guest either as they were all greyed out on VM’s configurations page on WMWare Server. &lt;/p&gt;  &lt;p&gt;Now I am happily running Ubuntu 10.04 on VirtualBox and &lt;a href="http://linuxmint.com/download.php"&gt;Linux Mint 9 Isadora&lt;/a&gt; – both 32-bit of course &lt;a href="http://www.mysmiley.net/free-angel-smileys.php" title="angel smileys"&gt;&lt;img src="http://serve.mysmiley.net/sad/sad0049.gif" alt="angel smileys" border="0"&gt;&lt;/a&gt;.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7903484898431369984?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7903484898431369984/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7903484898431369984' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7903484898431369984'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7903484898431369984'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/07/virtual-machines.html' title='Virtual Machines'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7249968807652057684</id><published>2010-06-07T22:33:00.002+10:00</published><updated>2010-06-07T22:48:28.943+10:00</updated><title type='text'>Eclipse Galileo vs. NetBeans 6.8</title><content type='html'>&lt;p&gt;I have read and heard about the praises on both Eclipse and NetBeans on how performant they each are, especially when a new release comes out. Today I had a chance to witness the two with my own eyes and compare them.
&lt;/p&gt;&lt;p&gt;
I received a hideous wsdl file containing 700+ operations and hundreds of data types. I first tried to generate the java client files in NetBeans using JAXB, but it complained about some duplicated type. So I tried with Eclipse (Galileo). It generated the Java client files using Axis2 without any problems. The files and organised in packages according to the wsdl file and everything was neat and compiled normally.
&lt;/p&gt;&lt;p&gt;
Didn’t want to give up on NetBeans so I tried to generate the Java client using JAX-RPC style. To do this I had to install the missing plugins, which I did. Then I started to generate the files, NetBeans seemed stuck – the GUI became unresponsive. After a good half an hour or more it came back and reported out of memory error. I realised that it had generated the java files but could not compile due to low memory. So I restarted it and tried to compile again, then it complained about a code being too large – to be fair this is the problem of wsimport which is used by NetBeans to generate the web services client files. So I manually split the big SerializerRegistry method (containing 9000+ lines and probably went over the 64k limit on method size) into two and recompiled. This time it worked. But the whole code editor remains sluggish.
&lt;/p&gt;&lt;p&gt;
Examining the memory usage of both Eclipse and NetBeans with their respective web services client project open, Eclipse occupies about 255M and NetBeans 520M.
&lt;/p&gt;&lt;p&gt;
My experience tells me that tooling wise Asix2 is better than JDK’s own web services tools; Eclipse Galileo is more memory efficient than NetBeans 6.8.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7249968807652057684?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7249968807652057684/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7249968807652057684' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7249968807652057684'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7249968807652057684'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/06/eclipse-galileo-vs-netbeans-68.html' title='Eclipse Galileo vs. NetBeans 6.8'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-570873682334959031</id><published>2010-04-11T11:19:00.001+10:00</published><updated>2010-04-11T11:29:02.896+10:00</updated><title type='text'>Nokia Lost the Plot</title><content type='html'>&lt;p&gt;Nokia has abandoned the 4-digit naming convention for its mobile phone models and adopted &lt;a href="http://sisdown.com/article/67461.html"&gt;four series of N, E, C, X&lt;/a&gt; instead. The N series is supposed to be the top notch flag-ship products of Nokia with the latest and greatest technologies. The X series are supposed to be the device for social, entertainment and youth. However, the way Nokia allocates the features to their devices is quite messy and inconsistent.&lt;/p&gt;  &lt;p&gt;The 5800 XM is supposed to be a great music player. However, it does not have the Home Media application although the machine is equipped with uPnP capability. Also, it allows you to configure SIP settings but there is no built-in SIP client! On the contrary, N95 has both features built-in.&lt;/p&gt;  &lt;p&gt;I was ecstatic when I heard about Nokia freeing up its Ovi Map including voice turn by turn navigation since late January 2010. So I quickly went to &lt;a href="http://maps.nokia.com"&gt;Nokia’s official maps site&lt;/a&gt; and installed them on my N95. In order to install Ovi Map, it had to upgrad my N95’s software version and wiped out all my applications, history and address book during the process! So I had to spend days to re-patch my phone, re-install all the software and games, re-download and reload the map and voice files and restore from a 2-year-old address book backup… But hey the Ovi Map (v3.01 09wk44 b01) got installed and actually worked. But my joy was short lived. My expired trial license for navigation did not get renewed or removed. So when I tried to start navigation, Ovi Map will nag me about purchasing licenses and if I don’t it will quit navigation. So meanwhile I have to feel content with TomTom 6 and its 3-year-old Australian map.&lt;/p&gt;  &lt;p&gt;Frustrated, I did some digging and found out that the ‘Free Forever’ maps is not compatible with my N95 – as it is not in the device list. Yet you can still download the software and maps for it – it is just not free! However, Nokia’s marketing machine does not tell you this. No wonder so &lt;a href="http://discussions.europe.nokia.com/t5/Maps-Navigation-and-GPS/Ovi-Maps-3-03-Free-Navigation-for-N95-amp-N96/td-p/623429"&gt;many N95 users share this frustration&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;A glimpse of hope remains for my 5800 XM as it is in the Ovi Map compatible devices list. To install the free version (v3.03 which only works on S60v3 FP2 and S60v5 and it is designed/optimised for touch screen user interface) I need to upgrade the phone’s software to 31.0.008 or greater. I have yet to do so as I want to make sure I back things up before the upgrade…&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-570873682334959031?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/570873682334959031/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=570873682334959031' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/570873682334959031'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/570873682334959031'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/04/nokia-lost-plot.html' title='Nokia Lost the Plot'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8898512462962004953</id><published>2010-04-04T11:21:00.001+10:00</published><updated>2010-04-04T14:11:24.655+10:00</updated><title type='text'>Using VoIP on Nokia</title><content type='html'>&lt;p&gt;For years my family had relied on calling cards for making ‘cheap’ overseas calls. But these days most of the calling card services are notoriously unreliable and those companies downright unscrupulous – they promise you thousands of minutes, but you’d be lucky if it lasts more than an hour. Sometimes the company will even wind up before you can use your newly purchased calling card!&lt;/p&gt;  &lt;p&gt;The good news is that there are more nimble telco providers in Australia that provide value for money. The ones that are suitable for my family’s needs are &lt;a href="http://www.lebara-mobile.com.au/"&gt;Lebara&lt;/a&gt; and &lt;a href="http://www.pennytel.com.au"&gt;PennyTel&lt;/a&gt;. Lebara offers cheap rates to overseas calls especially to landlines but with a 25c flagfall charge. PennyTel on the other hand has better rates to overseas overall and does not have any flagfall charges.&lt;/p&gt;  &lt;p&gt;I like PennyTel for its competitive rates and its usage of standard SIP technology as its VoIP platform. &lt;a href="http://www.forum.nokia.com/Technology_Topics/Mobile_Technologies/VoIP/Nokia_VoIP_Framework/VoIP_support_in_Nokia_devices.xhtml"&gt;SIP is available on many Nokia phones&lt;/a&gt;.&amp;#160; On my N95 it is as simple as following the &lt;a href="https://www.pennytel.com/doc/nokia_e65.pdf"&gt;instructions provided on PennyTel&lt;/a&gt;. Then you can call any landland numbers (need use either area code + number or country code + area code + number). Receiving calls to your PennyTel number also works well (calling from a PennyTel ATA device). However, I did find that calling other PennyTel numbers (either landline or account number) using its softphone or SIP clients on my mobile did not work – it either would not connect or gave me announcements…&lt;/p&gt;  &lt;p&gt;Using SIP on XM 5800 is another story. Although the phone does have a place for you to configure SIP settings (Menu -&amp;gt; Settings -&amp;gt; Connectivity -&amp;gt; Admin. Settings -&amp;gt; SIP Settings) but there is no built-in SIP client software. Alas, XM 5800 is not in the &lt;a href="http://www.forum.nokia.com/Technology_Topics/Mobile_Technologies/VoIP/Nokia_VoIP_Framework/VoIP_support_in_Nokia_devices.xhtml"&gt;table&lt;/a&gt;. So I installed &lt;a href="http://www.fring.com/default.php"&gt;Fring&lt;/a&gt; on my XM 5800 and the SIP configuration is even simpler – only 3 fields to fill.&lt;/p&gt;  &lt;p&gt;On PC (Windows 7) I have tried &lt;a href="http://sip-communicator.org/"&gt;SIP Communicator&lt;/a&gt; and &lt;a href="http://www.counterpath.com/x-lite-4.0-for-windows-download.html"&gt;X-Lite&lt;/a&gt;. SIP Communicator supports IM and VoIP from various social networks while X-Lite is specialised in SIP. Both worked well. But X-Lite seems more polished. I have used &lt;a href="http://www.counterpath.com/eyebeam.html"&gt;eyeBeam&lt;/a&gt; – the commercial version of X-Lite for work and quite liked it – it has hundreds of parameters you can tweak provided that you know your SIP and RTP well.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8898512462962004953?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8898512462962004953/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8898512462962004953' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8898512462962004953'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8898512462962004953'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/04/using-voip-on-nokia.html' title='Using VoIP on Nokia'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5136811423702562139</id><published>2010-03-18T12:24:00.001+11:00</published><updated>2010-03-18T12:24:41.734+11:00</updated><title type='text'>Whatever Happened to Programming?</title><content type='html'>&lt;p&gt;Mike Taylor wrote two insightful essays about programming a few days ago. Are we stuck in a tedious loop of gluing APIs together? What happened to the creative aspects of programming? &lt;/p&gt;  &lt;p&gt;&lt;a href="http://reprog.wordpress.com/2010/03/03/whatever-happened-to-programming/"&gt;Whatever happened to programming part 1&lt;/a&gt; talks about how the most enjoyable part of a new project occurs early on, when things are just taking shape and the most creative effort takes place. &lt;a href="http://www.theserverside.com/#"&gt;Whatever happened to programming redux&lt;/a&gt; is a response to many comments he received so far, with more insightful commentary.&lt;/p&gt;  &lt;p&gt;I don’t think libraries or frameworks are boring to use. Actually I find some of them quite insightful and fun to use. Comparing to the old days (for me that was early 1990’s) when I had to spend time writing our own C libraries to implement data structures or do network communications, now I have heaps more than what I will ever need built-in in JRE or .NET or even ActionScript. The upside of these libraries are quality and productivity. The main complaint I have about frameworks is that it can limit your choice – see &lt;a href="http://romenlaw.blogspot.com/2010/03/not-happy-gae.html"&gt;my gripe about GAE&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;I &lt;a href="http://romenlaw.blogspot.com/2009/12/enterprise-architect.html"&gt;have said before&lt;/a&gt; that the IT industry is modelled after real engineering disciplines like built environment. In &lt;a href="http://en.wikipedia.org/wiki/Built_environment"&gt;built environment&lt;/a&gt;, when you want to build a house you will have different people with different expertise in their own area to be involved from concept to design to build and test. When an engineer looks at a design, he/she will choose the materials and the trades people will actually ‘glue’ them together. As a builder, you will not expect to make your own glass, bake your own bricks and tiles, grow your own grass, etc. All are readily available for you to pick and choose and ‘glue’ together.&lt;/p&gt;  &lt;p&gt;Sure, if you are stranded on a deserted island you will have to start from raw materials to make tools and build simple structures assuming you know (or can teach yourself) how. But that does not have the quality and productivity that are required in a normal environment. Another big driving factor is that&amp;#160; for most of us who write commercial applications (not tool smith) we need to serve a business need. IT does not exist without a business diver. In some cases, I felt bored about the frameworks that have been chosen by the company. That is why I do a lot of playing after work &lt;img class="inlineimg" title="Wink" border="0" alt="" src="http://www.sunniforum.com/forum/images/smilies/icon_wink.gif" /&gt;&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5136811423702562139?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5136811423702562139/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5136811423702562139' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5136811423702562139'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5136811423702562139'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/03/whatever-happened-to-programming.html' title='Whatever Happened to Programming?'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7585955709400059828</id><published>2010-03-08T12:23:00.001+11:00</published><updated>2010-03-08T12:26:54.650+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><category scheme='http://www.blogger.com/atom/ns#' term='Cloud Computing'/><title type='text'>Not Happy, GAE!</title><content type='html'>&lt;p&gt;Cloud Computing has been a latest buzz word recently and seemingly will be for the foreseeable future. I had registered for &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt; (GAE) a while ago but did not do anything with it until last week.&lt;/p&gt;  &lt;p&gt;I downloaded the &lt;a href="http://dl.google.com/eclipse/plugin/3.5"&gt;Google Plugin for Eclipse 3.5 (Galileo)&lt;/a&gt; and installed it without any problem. I then decided to port one of my web applications written in &lt;a href="http://www.zkoss.org/"&gt;ZK 3.6.2&lt;/a&gt; with &lt;a href="http://static.springsource.org/spring-security/site/"&gt;Spring Security&lt;/a&gt; 3.0.2 in its simplest form.&lt;/p&gt;  &lt;p&gt;I started by creating a GAE application in Eclipse using the Google plugin’s wizard. I then copied &lt;code&gt;WebContent&lt;/code&gt; directory from my source project to the &lt;code&gt;war&lt;/code&gt; directory of the target GAE project. I modified the deployment descriptors according to instructions from &lt;a href="http://docs.zkoss.org/wiki/ZK/How-Tos/Installation/How_to_Integrate_ZK_with_Google_App_Engine"&gt;ZK small talk&lt;/a&gt; with the following additions:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;&lt;code&gt;appengine-web.xml&lt;/code&gt; – I added &lt;code&gt;/**.xml&lt;/code&gt; in the static file exclude path list since in my application I generate .xml files to feed them to the &lt;a href="http://www.maani.us/gauge/"&gt;XML/SwfGauge&lt;/a&gt; package. &lt;/li&gt;    &lt;li&gt;&lt;code&gt;applicationContext-security.xml&lt;/code&gt; – the Spring Security deployment descriptor. I had to add a URL pattern for “/” explicitly due to the different way of handling it by Tomcat (the server I used for developing the web app) and Jetty (the server bundled in Google Plugin). &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;The modified application then ran quite successfully as GAE application in Eclipse. Of course, anything that involves creating user thread were disallowed and any Java packages/classes that were not supported by GAE were also not allowed. I thought I could work around those problems later. &lt;/p&gt;  &lt;p&gt;Then I deployed the application to the real GAE. The deployment process was error free. I was overjoyed and clicked on the URL for my application on the appspot.com. A HTTP 500 – Server error was returned without any explanation.&amp;#160; I did a little fiddling and my first page was displayed if I remove Spring Security, but then all my included pages (I store all my pages in &lt;code&gt;WEB-INF/pages&lt;/code&gt; directory) were not shown… I would need at least a few dozen of trial and error before I can pin-point the problem but my internet connection quota is running out fast! So I will call it quits for now.&lt;/p&gt;  &lt;p&gt;While cloud computing gives you great promises it is also a shackle and limits your freedom of choice . Although GAE seems to be &lt;a href="http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine"&gt;compatible with many frameworks&lt;/a&gt;, the degree of compatibility varies. For example, in the &lt;a href="http://groups.google.com/group/google-appengine-java/web/will-it-play-in-app-engine"&gt;list&lt;/a&gt; ZK framework is &lt;font color="#00ff40"&gt;COMPATIBLE&lt;/font&gt; with GAE, but if you look at all the features and packages that are included in ZK you will quickly realise that it is not true – server push requires separate thread and so does jFreeChart and some other bundled packages; captcha uses java.awt.Font (which is not allowed in GAE)… So you’d be better off sticking with GAE’s recommended technology stack – servlet/JSP/JSF + JPA/JDO + JAXB + XML, etc.&amp;#160; This may be OK for new applications but presents a big obstacle for existing large applications which heavily rely on other frameworks.&lt;/code&gt;&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7585955709400059828?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7585955709400059828/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7585955709400059828' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7585955709400059828'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7585955709400059828'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/03/not-happy-gae.html' title='Not Happy, GAE!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-6873772069744500576</id><published>2010-02-11T12:47:00.001+11:00</published><updated>2010-02-11T15:07:07.245+11:00</updated><title type='text'>Happy New Year of the Tiger!</title><content type='html'>&lt;img src='http://whjzc.3996.cc:909/whjzc/36163_200951614425635701.gif'/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-6873772069744500576?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/6873772069744500576/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=6873772069744500576' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6873772069744500576'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6873772069744500576'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/02/happy-new-year-of-tiger.html' title='Happy New Year of the Tiger!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1217553178683797431</id><published>2010-01-30T10:44:00.001+11:00</published><updated>2010-01-30T10:55:33.622+11:00</updated><title type='text'>VPN Client for Windows 7</title><content type='html'>&lt;p&gt;I have to use Cisco VPN Client v4.6.* for work. It works fine on Windows XP. Recently, as one of my Windows XP laptops went kaput, I upgraded to Windows 7 and am loving it. However, the Cisco VPN client would not work anymore. I tried to download a newer version (v5.x) from Cisco, but for several months their online registration just would not work (hard to imagine such a giant company couldn’t get such a simple thing working). So I had to hunt for alternatives.&lt;/p&gt;  &lt;p&gt;Now I am using &lt;a href="http://www.shrew.net/download"&gt;ShrewSoft VPN Client&lt;/a&gt;. It is extremely simple and fast to install and configure. The whole experience from download, installation, configuration to use took less than 10 minutes. It supports importing of Cisco VPN connection profiles (.pcf files). It has good &lt;a href="http://www.shrew.net/support"&gt;online documentation&lt;/a&gt;, and its &lt;a href="http://www.shrew.net/about"&gt;free&lt;/a&gt;!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1217553178683797431?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1217553178683797431/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1217553178683797431' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1217553178683797431'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1217553178683797431'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/01/vpn-client-for-windows-7.html' title='VPN Client for Windows 7'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4746653033883325791</id><published>2010-01-26T11:53:00.001+11:00</published><updated>2010-01-26T12:05:19.140+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='Flash'/><title type='text'>Flash vs. JavaFX</title><content type='html'>&lt;p&gt;I wrote the same eCards &lt;a href="http://romenlaw.blogspot.com/2008/12/happy-new-year.html"&gt;using JavaFX 1.0&lt;/a&gt; in NetBeans and &lt;a href="http://romenlaw.blogspot.com/2010/01/happy-new-year-2010.html"&gt;Flash 10 (with ActionScript 3)&lt;/a&gt; in CS4. The contents and algorithms of the eCards are the same but the outcome are quite different.&lt;/p&gt;  &lt;h2&gt;User Experience&lt;/h2&gt;  &lt;ol&gt;   &lt;li&gt;The Flash version has 200 snow flakes and the JavaFX one has 30. The result is that Flash eCard is very smooth and the JavaFX one is jerky. &lt;/li&gt;    &lt;li&gt;If your browser does not have the required Flash plug-in, you can still view the rest of the page; but JavaFX forces your browser to go to Sun to download the plugin and you cannot see the other contents of the page. &lt;/li&gt;    &lt;li&gt;The first time you view the pages, the .swf file and .jar files will be downloaded to your computer respectively. The time taken to download them are not that much different with Flash version slightly shorter. However, if you view them afterwards, the cached Flash eCard will be played instantaneously without having to download again even after you restart the browser or computer. But the JavaFX version will be downloaded again every time. &lt;/li&gt; &lt;/ol&gt;  &lt;h2&gt;Designer Experience&lt;/h2&gt;  &lt;ol&gt;   &lt;li&gt;The CS4 environment is a lot easier to use for designers. You can do things graphically or programmatically. In NetBeans you have no choice but to write code for everything – even to manage timelines! &lt;/li&gt;    &lt;li&gt;CS4 generates deployment code/files for HTML and AIR. It is really easy to deploy .swf files and the process is simple, robust and mature. I can’t say the same thing about Java Web Start, which is &lt;a href="http://java.dzone.com/news/when-will-java-web-start-be"&gt;nowhere near production quality&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;If you are like me, who wants to learn things from free web resources, then JavaFX is easier since there is only one way to do things – by writing code (this could be a bad thing for some). Most of the tutorials and sample codes work. But the different ActionScript releases are not backward-compatible. Many of the code samples were written in AS 1.0 or 2.0 (since they have been out there for many years) – they do not work in 3.0. &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;&lt;strong&gt;Related Post&lt;/strong&gt;:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/12/sorry-state-of-javafx-10.html"&gt;The Sorry State of JavaFX 1.0&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2009/01/javafx-mediaplayer-memory-leak.html"&gt;JavaFX MediaPlayer Memory Leak&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/12/happy-new-year.html"&gt;Happy New Year 2009&lt;/a&gt; &lt;/li&gt;    &lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2010/01/happy-new-year-2010.html"&gt;Happy New Year 2010&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4746653033883325791?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4746653033883325791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4746653033883325791' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4746653033883325791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4746653033883325791'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/01/flash-vs-javafx.html' title='Flash vs. JavaFX'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5807613166201899628</id><published>2010-01-22T11:25:00.001+11:00</published><updated>2010-01-22T11:25:53.534+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Rise of the Droid</title><content type='html'>&lt;p&gt;Traffic to this blog had been pretty flat in the last couple of months. It was not surprising considering the holiday season. However, I noticed an unusual surge of traffic in the last three weeks creating new record highs. I was a bit baffled. The only thing mildly interesting (to me) that I wrote after the New Year was about &lt;a href="http://romenlaw.blogspot.com/2010/01/hacking-linkedin-api.html"&gt;hacking LinkedIn API&lt;/a&gt;. I don't think many netizens are that interested in LinkedIn, let alone LinkedIn API. &lt;/p&gt;  &lt;p&gt;A little digging in my custom reports in &lt;a href="https://www.google.com/analytics"&gt;Google Analytics&lt;/a&gt; revealed that my No. 1 visited blog was &lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-from-android.html"&gt;Consumer Web Services from Android&lt;/a&gt; that I wrote in 2008 when the Android 0.9 SDK Beta was released. For about 12 months, that post was not among the &lt;a href="http://romenlaw.blogspot.com/2009/06/one-year-on.html"&gt;top 5 list&lt;/a&gt;. Yet soon after &lt;a href="http://news.cnet.com/8301-30684_3-10424433-265.html"&gt;Google announced its launch of Android phone&lt;/a&gt; people are are beefed up about development for Android platform.&amp;#160; Judging from the developer community’s enthusiasm, it won’t take long before tons of Android applications to be available, helping Android’s market share. So I expect to see Android to catch up with iPhone in the next 12-24 months. It will be interesting to see how the multi-vendor strategy work out against Apple’s iPhone.&lt;/p&gt;  &lt;p&gt;Kudos to &lt;a href="http://googleblog.blogspot.com/2010/01/new-approach-to-china.html"&gt;Google&lt;/a&gt;!&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5807613166201899628?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5807613166201899628/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5807613166201899628' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5807613166201899628'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5807613166201899628'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/01/rise-of-droid.html' title='Rise of the Droid'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-2114870399892994265</id><published>2010-01-15T21:36:00.009+11:00</published><updated>2010-03-15T11:24:53.539+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Flash'/><title type='text'>Happy New Year 2010</title><content type='html'>&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=10,0,0,0" width="365" height="490" id="2010" align="middle"&gt; 	&lt;param name="allowScriptAccess" value="sameDomain" /&gt; 	&lt;param name="allowFullScreen" value="false" /&gt; 	&lt;param name="movie" value="http://public.blu.livefilestore.com/y1p6s8DM7sYrvwbdrBewQpkz2tEElR0GLFI3J1j5XlmdfwbjjmI_-kIEh1VnRkuhnjjLOgG1lw9a3cV49WeBd1UIQ/2010.swf" /&gt;&lt;param name="loop" value="false" /&gt;&lt;param name="quality" value="high" /&gt;&lt;param name="bgcolor" value="#0000a5" /&gt;	&lt;embed src="http://public.blu.livefilestore.com/y1pgKx6E5gufOWwm7Ep1qBScCIvJ4W2WIj5bCKSwF2GCx0CC59xM73UZFCBH1T9Nu50Ynpd96BY6tY3TybBQoi0Bg/2010.swf" loop="false" quality="high" bgcolor="#0000a5" width="365" height="490" name="2010" align="middle" allowScriptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer" /&gt; 	&lt;/object&gt;  &lt;p&gt;This is a Flash port of my eCard first written in JavaFX last year. &lt;/p&gt;  &lt;p&gt;The snow fall script is stolen from &lt;a title="http://r3dux.org/2010/01/actionscript-3-0-particle-systems-2-snow-effect/&amp;#13;&amp;#10;" href="http://r3dux.org/2010/01/actionscript-3-0-particle-systems-2-snow-effect/"&gt;http://r3dux.org/2010/01/actionscript-3-0-particle-systems-2-snow-effect/&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;The algorithm of the snow fall between the the JavaFX version and this one are pretty similar. The background photo was taken in Darling Harbour on Christmas eve 2009.   &lt;br /&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;  &lt;p&gt;&lt;b&gt;Related Post:&lt;/b&gt; &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/12/happy-new-year.html"&gt;Happy New Year 2009&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-2114870399892994265?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/2114870399892994265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=2114870399892994265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2114870399892994265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2114870399892994265'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/01/happy-new-year-2010.html' title='Happy New Year 2010'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7580506177099920030</id><published>2010-01-02T15:31:00.009+11:00</published><updated>2011-05-17T12:37:10.861+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='LinkedIn'/><category scheme='http://www.blogger.com/atom/ns#' term='Hacking'/><title type='text'>Hacking LinkedIn API</title><content type='html'>&lt;p&gt;&lt;strong&gt;Note [2011-03-24]&lt;/strong&gt;: &lt;em&gt;As promised, I have updated the source code. Now it works! Please see my other post which supercedes this one - &lt;a href="http://romenlaw.blogspot.com/2011/03/hacking-linkedin-api-take-2.html"&gt;Hacking LinkedIn API - Take 2&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Note [2010-03-12]&lt;/strong&gt;:&lt;em&gt;LinkedIn has changed the login page(s) so that the HTML scraping code below no longer works. However, I do believe that the approach still works. I will publish an update when I have some time after hours. Stay tuned.&lt;img class="inlineimg" title="Wink" border="0" alt="" src="http://www.sunniforum.com/forum/images/smilies/icon_wink.gif" /&gt;&lt;/em&gt;&lt;/p&gt;&lt;p&gt;I was quite excited to discover that &lt;a href="http://au.linkedin.com/in/romen"&gt;LinkedIn&lt;/a&gt; was exposing &lt;a href="http://developer.linkedin.com/community/apis"&gt;APIs&lt;/a&gt; for 3rd-party applications to tap into its data. I downloaded the &lt;a href="http://linkedin-j.googlecode.com/files/linkedin-j-0.1-SNAPSHOT-2009-12-30.zip"&gt;LinkedIn-J 0.1 SNAPSHOT-2009-12-30&lt;/a&gt; which is a beta java wrapper of the APIs from &lt;a href="http://code.google.com/p/linkedin-j/downloads/list"&gt;Google Code&lt;/a&gt; and tried the &lt;a href="http://developer.linkedin.com/message/2572"&gt;example&lt;/a&gt; posted at LinkedIn Developer Network forum. The sample worked quite well as intended.&lt;/p&gt;&lt;p&gt;However, the paradigm of the oauth procedure which is adopted by LinkedIn APIs is convoluted and the LinkedIn authorisation process assumes your application is a web application. As part of the authorisation process, it forces the user to login directly to LinkedIn web page (shown below) to get a PIN and feed the PIN to the application to continue the process.&lt;/p&gt;&lt;p&gt;&lt;img src="http://dtt0ua.blu.livefilestore.com/y1p4JiBVXkWgkAOA4wma9nt1l7VOpsoGJDTPFpPKa2h7LYqm-00gw6AQ6ZNzrHeFkJCJJw9t8IPdgoc7FwSteYJOwdwVhEgumht/linkedIn.png" /&gt; &lt;/p&gt;&lt;p&gt;Once logged in successfully, a PIN is given in the next HTML page:&lt;/p&gt;&lt;p&gt;&lt;img src="http://dtt0ua.blu.livefilestore.com/y1pZ9z1jB1Ds9f8XhpXfiUWSPScjD0J6jS8YlAvJed3E9sFQGPsfCGv5bYkGcWFpzJPPhgUVNzxkWgUYZycf6lFegBQiJJVD-8s/linkedIn2.png" /&gt; &lt;/p&gt;&lt;p&gt;This is no good for machine-to-machine type of integration. I just want to retrieve LinkedIn data from the backend without forcing the user to be a LinkedIn member or login twice. So I decided to bypass this extra login.&lt;/p&gt;&lt;p&gt;Looking (view source) at the above login form it is a simple HTML FORM:&lt;/p&gt;&lt;pre class="html" name="code"&gt;...
         &lt;form method="post" name="login"&gt;&lt;ul class="formset"&gt;&lt;li&gt;                 &lt;label for="email"&gt;Email Address:&lt;/label&gt;                 &lt;div class="field"&gt;&lt;input class="text" name="email"&gt;&lt;/div&gt;&lt;li&gt;                 &lt;label for="password"&gt;LinkedIn Password:&lt;/label&gt;                 &lt;div class="field"&gt;&lt;input class="text" value="" type="password" name="password"&gt;&lt;/div&gt;&lt;!--  TODO replace with proper spacing --&gt;&lt;br /&gt;
&lt;li&gt;                 &lt;label for="duration"&gt;Access Duration:&lt;/label&gt;                 &lt;div class="field"&gt;&lt;select name="duration"&gt; &lt;option value="0"&gt;Until Revoked&lt;/option&gt; &lt;option value="720"&gt;Thirty Days&lt;/option&gt; &lt;option value="168"&gt;One Week&lt;/option&gt; &lt;option value="24"&gt;One Day&lt;/option&gt;&lt;/select&gt;                 &lt;/div&gt;&lt;!--  TODO replace with proper spacing --&gt;&lt;br /&gt;
&lt;li class="single"&gt;&lt;input class="btn-primary" value="Grant Access" type="submit" name="authorize"&gt; or &lt;a href="http://www.blogger.com/"&gt;Return to MyApplication&lt;/a&gt;&lt;br /&gt;
&lt;li class="single"&gt;&lt;a class="helper" href="http://www.linkedin.com/passwordReset"&gt;I forgot my password&lt;/a&gt;             &lt;/li&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;/ul&gt;&lt;input type="hidden" name="extra"&gt;             &lt;input value="true" type="hidden" name="agree"&gt;             &lt;input value="-3" type="hidden" name="access"&gt;             &lt;?xml:namespace prefix = leo /&gt;&lt;leo:cookiedisabledmessage message="Make sure you have cookies and Javascript enabled in your browser before signing in."&gt;&lt;/leo:cookiedisabledmessage&gt;           &lt;/form&gt;...&lt;/pre&gt;&lt;p&gt;Note that the FORM has the following INPUT elements (including the hidden ones and the submit button):&lt;/p&gt;&lt;ol&gt;&lt;li&gt;email – use my login to LinkedIn &lt;/li&gt;
&lt;li&gt;password – use my password to LinkedIn &lt;/li&gt;
&lt;li&gt;duration – set to 0 &lt;/li&gt;
&lt;li&gt;access – set to –3 &lt;/li&gt;
&lt;li&gt;agree – set to true &lt;/li&gt;
&lt;li&gt;extra – empty &lt;/li&gt;
&lt;li&gt;authorize – set to Grant Access &lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;We can easily populate this form and submit it in Java. &lt;/p&gt;&lt;pre class="java" name="code"&gt;...
DataOutputStream dataOut;
      
        try {
            URL url = new URL(authUrl);
            //URL url = new URL("https://api.linkedin.com/uas/oauth/authorize");
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("POST");
            con.setUseCaches(false);
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            dataOut = new DataOutputStream(con.getOutputStream());

            dataOut.writeBytes("email=myname%40mycompany.com&amp;amp;password=mypassword&amp;amp;duration=0&amp;amp;access=-3&amp;amp;agree=true&amp;amp;extra=&amp;amp;authorize=Grant Access");
            dataOut.flush();
            dataOut.close();

            //SSLException thrown here if server certificate is invalid
            String returnedHtml=convertStreamToString(con.getInputStream());
            System.out.println(returnedHtml);
...&lt;/pre&gt;&lt;p&gt;If successful, the &lt;code&gt;returnedHTML&lt;/code&gt; will contain the PIN (as shown in screenshot above). So it is simply a matter of scraping that HTML string to get the 5-digit PIN and feed it to the next step of the authorisation process. The following is the modified &lt;a href="http://64.74.98.87/message/2572"&gt;Beta Java SignPost Sample Code&lt;/a&gt; to bypass the LinkedIn login page:&lt;/p&gt;&lt;pre class="brush: java; highlight: [27, 96, 98];" name="code"&gt;// LinkedIn SignPost Sample Code
// Adapted by Taylor Singletary
// from Twitter SignPost Sample Code ( http://oauth-signpost.googlecode.com/files/OAuthTwitterExample.zip )
// Tested and Functional with attached SignPost JAR
// YMMV

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.logging.Level;

import oauth.signpost.OAuth;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthConsumer;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.signature.SignatureMethod;


public class Main {
    static public String getPin(String authUrl) {
        DataOutputStream dataOut;
      
        try {
            URL url = new URL(authUrl);
            //URL url = new URL("https://api.linkedin.com/uas/oauth/authorize");
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("POST");
            con.setUseCaches(false);
            con.setDoInput(true);
            con.setDoOutput(true);
            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            dataOut = new DataOutputStream(con.getOutputStream());

            dataOut.writeBytes("email=myname%40mycompany.com&amp;amp;password=mypassword&amp;amp;duration=0&amp;amp;access=-3&amp;amp;agree=true&amp;amp;extra=&amp;amp;authorize=Grant Access");
            dataOut.flush();
            dataOut.close();

            //SSLException thrown here if server certificate is invalid
            String returnedHtml=convertStreamToString(con.getInputStream());
            System.out.println(returnedHtml);
            /* extract the pin from the html string. the block looks like this
        &amp;lt;div class="content"&amp;gt;
          You have successfully authorized MyApplication
          Please enter the following security code to enable full access

          &amp;lt;p align="center"&amp;gt;&amp;lt;b&amp;gt;12345&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
             * It turns out that the whole html string only contains one 'p align="center"'
             * also it seems that the pin is always 5-digit long,
             * so we will just crudely detect that string and get the pin out.
             * A proper HTML parser should be used in a real application.
             */
            int i=returnedHtml.indexOf("center\"&amp;gt;&amp;lt;b&amp;gt;");
            String pin = returnedHtml.substring(i+11, i+11+5);
            System.out.println("pin="+pin);
            return pin;
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }
    public static void main(String[] args) throws Exception {
        OAuthConsumer consumer = new DefaultOAuthConsumer(
                "YourConsumerKey",
                "YourConsumerSecret",
                SignatureMethod.HMAC_SHA1);

        OAuthProvider provider = new DefaultOAuthProvider(consumer,
                "https://api.linkedin.com/uas/oauth/requestToken",
                "https://api.linkedin.com/uas/oauth/accessToken",
                "https://api.linkedin.com/uas/oauth/authorize");

        System.out.println("Fetching request token from LinkedIn...");

        // we do not support callbacks, thus pass OOB
        String authUrl = provider.retrieveRequestToken(OAuth.OUT_OF_BAND);

        System.out.println("Request token: " + consumer.getToken());
        System.out.println("Token secret: " + consumer.getTokenSecret());
/*
        System.out.println("Now visit:\n" + authUrl
                + "\n... and grant this app authorization");
        System.out.println("Enter the PIN code and hit ENTER when you're done:");

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String pin = br.readLine();
*/
        System.out.println("Now getting PIN from:\n" + authUrl);
      
        String pin = getPin(authUrl);
        System.out.println("Fetching access token from LinkedIn...");

        provider.retrieveAccessToken(pin);

        System.out.println("Access token: " + consumer.getToken());
        System.out.println("Token secret: " + consumer.getTokenSecret());

        URL url = new URL("http://api.linkedin.com/v1/people/~:(id,first-name,last-name,picture-url,headline)");
        HttpURLConnection request = (HttpURLConnection) url.openConnection();

        consumer.sign(request);

        System.out.println("Sending request to LinkedIn...");
        request.connect();
        String responseBody = convertStreamToString(request.getInputStream());

        System.out.println("Response: " + request.getResponseCode() + " "
                + request.getResponseMessage() + "\n\n" + responseBody);
       
    }

    // Stolen liberally from http://www.kodejava.org/examples/266.html
    public static String convertStreamToString(InputStream is) {
        /*
         * To convert the InputStream to String we use the BufferedReader.readLine()
         * method. We iterate until the BufferedReader return null which means
         * there's no more data to read. Each line will appended to a StringBuilder
         * and returned as String.
         */
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return sb.toString();
    }
}&lt;/pre&gt;&lt;em&gt;&lt;/em&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7580506177099920030?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7580506177099920030/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7580506177099920030' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7580506177099920030'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7580506177099920030'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2010/01/hacking-linkedin-api.html' title='Hacking LinkedIn API'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5340065091263284480</id><published>2009-12-31T22:47:00.001+11:00</published><updated>2010-01-01T11:06:28.805+11:00</updated><title type='text'>Happy New Year!</title><content type='html'>&lt;p&gt;As I look back the year that was 2009 I must say that I can’t complain much. I have achieved not too far from what I had expected. I can summarise my achievements in 2009 as&amp;#160; following&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Work – I still got my job! &lt;/li&gt;    &lt;li&gt;After Work – I learnt and used the ZK framework quite extensively and got conducted in the &lt;a href="http://docs.zkoss.org/wiki/Hall_of_Fame"&gt;ZK Hall of Fame&lt;/a&gt; and &lt;a href="http://docs.zkoss.org/wiki/Blogsphere"&gt;ZK Blogsphere&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Hobby – I have had much improvement in my piano playing compared to last year (when I just started learning). &lt;a href="http://romenlaw.blogspot.com/2009/09/confession-of-piano-shopper.html"&gt;I rewarded myself with a brand new Kawai K3&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Family – I had always travelled extensively in my previous jobs. So 2009 was the year that I spent the most time with my family since I started a family! &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Have a happy and prosperous 2010!&lt;/p&gt; &lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/gF-xm5y11Nk&amp;amp;hl=en&amp;amp;fs=1"&gt;&lt;/param&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;/param&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;/param&gt;&lt;embed src="http://www.youtube.com/v/gF-xm5y11Nk&amp;amp;hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;Related Posts&lt;/strong&gt;:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/12/happy-new-year.html"&gt;Happy New Year 2009&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/12/merry-christmas-javafx-10.html"&gt;Merry Christmas JavaFX 1.0&lt;/a&gt;&lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5340065091263284480?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5340065091263284480/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5340065091263284480' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5340065091263284480'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5340065091263284480'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/12/happy-new-year.html' title='Happy New Year!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7910197184491492706</id><published>2009-12-23T11:39:00.001+11:00</published><updated>2009-12-23T11:41:36.697+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Management'/><category scheme='http://www.blogger.com/atom/ns#' term='Architecture'/><title type='text'>Enterprise Architect</title><content type='html'>&lt;p&gt;In any IT forum, the topic of roles and skill sets of architects is a sure-fire flame bait. There are many roles with the title of ‘&lt;em&gt;architect&lt;/em&gt;’ in the IT industry and they can be quite different. I summarise the IT architect jobs into the following categories:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;glorified software engineers/business analysts – some employers and employment agencies alike beef up the job position to attract more experienced applicants; of course, some employees/applicants do the same to their profile to attract better pay.&lt;/li&gt;    &lt;li&gt;domain focused solution designers – these architects/designers focus on specific domains of the IT space. The boundaries may vary depending on how you slice and dice the space: infrastructure, security, integration; service fulfilment, network performance, inventory management, etc.&lt;/li&gt;    &lt;li&gt;enterprise wide architect – responsible for IT governance, IT strategy alignment with the business strategy. It overarches the various domains mentioned in point 2 above.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;The Enterprise Architect falls into the 3rd category. Not all organisations have or need to have such a dedicated role – a travel agency with 3 staff may well outsource their IT operations and the role of EA to an external party.&amp;#160; The role of EA is a product of a mature organisation (or mature org wannabees) that understands the importance of IT to their business.&amp;#160; &lt;a href="http://en.wikipedia.org/wiki/Enterprise_architect"&gt;Wikipedia&lt;/a&gt; has a pretty detailed description of what EA is. It uses the analogy of city planner (the EA) and domain specific designers and engineers in the building industry. This is a very good analogy and one that I often use. After all, the very term of &lt;em&gt;architect&lt;/em&gt; is borrowed from &lt;a href="http://en.wikipedia.org/wiki/Built_environment"&gt;built environment&lt;/a&gt; discipline.&lt;/p&gt;  &lt;p&gt;It is understandable that many people including IT professionals do not understand the roles of architects, especially EA. The main reason for this lack of understanding is that in many organisations the role of architect is shared with others – e.g. development team lead taking on the responsibility of system architect. When it comes to EA it becomes more illusive because not all companies have one. So it’s no wonder we see in various forums silly questions like should EA be doing coding&amp;#160; (btw, my answer to this question is that EA should not do coding as his/her day job; but it certainly helps for EA to learn new technologies to better understand them – so coding afterhours is great for EAs).&lt;/p&gt;  &lt;p&gt;The IT industry is very young and immature comparing to other disciplines liking medicine or built environment. As a result of reaching maturity, it is inevitable for the industry to become more and more specialised. Therefore, the roles and jobs become more and more fine-grained, focused and well-defined. No doubt, with IT becoming more mature, the roles of architect especially EA will become better defined and clearer to more people.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7910197184491492706?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7910197184491492706/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7910197184491492706' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7910197184491492706'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7910197184491492706'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/12/enterprise-architect.html' title='Enterprise Architect'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-814435537237469987</id><published>2009-12-15T16:26:00.001+11:00</published><updated>2009-12-15T19:01:12.088+11:00</updated><title type='text'>My Carbon Footprint</title><content type='html'>&lt;p&gt;As Copenhagen becomes the hotspot of global warming spat for the second week, it seems less likely anything substantial&amp;#160; can be achieved by the summit. However, one positive effect it has is for sure – it has raised the awareness of the global warming issue across the world.&lt;/p&gt;  &lt;p&gt;Just a few months ago there was a blackout in my house due to an unseasonal thunderstorm. I was home alone and suddenly felt so bored – without electricity there was no form of entertainment in the house! No TV, music, internet, game consoles and not even cooking! Whereas such blackouts were quite frequent where I lived during my childhood yet I quite enjoyed it for we got to have candles and play shadow puppets… So I decided to have a good old traditional form of entertainment by &lt;a href="http://romenlaw.blogspot.com/2009/09/confession-of-piano-shopper.html"&gt;buying a piano&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;Out of curiosity, I took part in calculating my carbon footprint using an &lt;a href="https://auscalc.footprintnetwork.org/ecological_footprint.swf"&gt;Ecological Footprints calculator&lt;/a&gt; adopting Australian model. It turns out that it would take 2.4 planet Earths to support my lifestyle if everyone on Earth lived like me, which is below the Australian average of 3 according to &lt;a href="http://www.wwf.org.au/footprint/"&gt;WWF Australia&lt;/a&gt;. &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_ILzIJXnrA40/SyceF21WLSI/AAAAAAAAADQ/TtmhTj5ZF0s/s1600-h/image%5B2%5D.png"&gt;&lt;img title="image" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="66" alt="image" src="http://lh4.ggpht.com/_ILzIJXnrA40/SyceHAWA8xI/AAAAAAAAADU/g9cX39zkIDQ/image_thumb.png?imgmax=800" width="244" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;However, if I factor in the fact that many of my consumption of resources are actually shared by my family, it only takes less than one Earth, which is ideal.&lt;/p&gt;  &lt;p&gt;Looking at the result, about half of the footprint comes from food. I tried to recalculate it with total vegan, the result was actually 8% worse. So my love of meet is actually good for the planet :)&lt;/p&gt;  &lt;p&gt;Another thing is that I fly a lot due to work. If I did not fly at all, my carbon footprint would reduce by about 8%. The good thing is that I don’t have to commute to work every day, which offsets my extra carbon emission. &lt;/p&gt;  &lt;p&gt;There are many things people can do to improve the situation. I think it all bog down to 3 things: be frugal, share, recycle. These concepts are all familiar to IT professionals like myself because we have to create systems that are preferment and cheap at the same time, although we call them by different names like algorithm efficiency, resource-pooling, package reuse, etc. Sharing (e.g. taking public transport) and recycling have received a lot of public attention. But one thing that is often overlooked is frugality (even in the IT world as hardware becomes cheaper and cheaper). Frugality is a virtue in east Asian traditional cultures. However, due to the western influence and economic boom in the region this virtue is in danger of being lost. When everything is being labelled with and measured in monetary units, it is very easy to miss the actual impact of wastage. &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-814435537237469987?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/814435537237469987/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=814435537237469987' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/814435537237469987'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/814435537237469987'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/12/my-carbon-footprint.html' title='My Carbon Footprint'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_ILzIJXnrA40/SyceHAWA8xI/AAAAAAAAADU/g9cX39zkIDQ/s72-c/image_thumb.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1099350708292128368</id><published>2009-11-11T01:14:00.007+11:00</published><updated>2009-11-11T01:41:40.880+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>The Wall</title><content type='html'>My last visit to the Berlin Wall was in 1998 while sections of the wall was still standing and Checkpoint Charlie was a tourist attraction. Today, as the west celebrates the 20th aniversary of the fall of the Berlin Wall, the Chinese netizens are joking about the &lt;a href="http://en.wikipedia.org/wiki/Golden_Shield_Project"&gt;Great Firewall of China &lt;/a&gt;(or GFW for short). I used to be sceptical about censorship in China - sure they block a few sites, but who cares? The Chinese can still visit majority of the websites, right? My view changed during my visit to China last month and personal experience of the wrath of the GFW.
&lt;p&gt;I first tried to access &lt;a href="http://romenlaw.blogspot.com"&gt;my blogger site &lt;/a&gt;and &lt;a href="http://picasaweb.google.com/romen.law"&gt;my picasa photo albums &lt;/a&gt;in an internet cafe in &lt;a href="http://maps.google.com.au/maps?f=q&amp;source=s_q&amp;hl=en&amp;q=&amp;vps=1&amp;jsv=186a&amp;sll=-25.335448,135.745076&amp;sspn=40.321106,56.337891&amp;ie=UTF8&amp;geocode=FezvVAIdNo_8Bg&amp;split=0"&gt;Tianjin&lt;/a&gt;. It turned out that they were not accessible. In fact, the whole &lt;a href="http://www.blogger.com"&gt;blogger.com &lt;/a&gt;was inaccessible! I thought this might be unique to that internet cafe, or Tianjin city. But I was wrong.
&lt;/p&gt;
&lt;p&gt;After a 25-hour train ride, I arrived in the southern city of &lt;a href="http://maps.google.com.au/maps?f=q&amp;source=s_q&amp;hl=en&amp;q=&amp;vps=2&amp;jsv=186a&amp;sll=39.151363,117.215881&amp;sspn=1.099034,1.760559&amp;g=tianjin&amp;ie=UTF8&amp;geocode=FfPrYAEdJ0fABg&amp;split=0"&gt;GuangZhou&lt;/a&gt;. I experienced the same problems! People told me that even for unblocked sites, certain contents (i.e. parts of the web page) of those sites can still be blocked.&lt;/p&gt;
&lt;p&gt;Now I understand why my blogger site has so few hits from China. The few red dots shown on the &lt;a href="http://www4.clustrmaps.com/counter/maps.php?url=http://romenlaw.blogspot.com"&gt;ClustrMaps &lt;/a&gt;are probably the result of automated search engines on the outside of the GFW.&lt;/p&gt;
&lt;p&gt;Curiously, MSN sites (blog, photos, file sharing, etc.) are not blocked by the GFW. Maybe Microsoft had better political connections than Google. Now I have to create new photo albums on MSN just for the Chinese!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1099350708292128368?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1099350708292128368/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1099350708292128368' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1099350708292128368'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1099350708292128368'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/11/wall.html' title='The Wall'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4474158352271021848</id><published>2009-10-24T22:29:00.003+11:00</published><updated>2009-10-24T22:57:23.880+11:00</updated><title type='text'>Bulk Photo Scaler</title><content type='html'>&lt;p&gt;After my holiday I captured hundreds of photos. I want to upload some of them to my &lt;a href="http://picasaweb.google.com.au/romen.law"&gt;online photo album&lt;/a&gt;. Before uploading the photos I need to scale the photos to a smaller size to speed up the file transfer and save my internet bandwidth. A popular bulk image processor - &lt;a href="http://cerebralsynergy.com/download.php?view.52"&gt;BIMP &lt;/a&gt;can be found from &lt;a href="http://cerebralsynergy.com/news.php"&gt;Cerebral Synergy&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;I have some pretty unique requirements to bulk scale these photos. They have been captured using different cameras - N95, XM5800, Canon, Olympus, Pentax, etc. - each having different image resolutions and therefore sizes. So I cannot just apply a single factor to all the pictures. Also, I have some panorama pictures shot with &lt;a href="http://romenlaw.blogspot.com/2008/11/n95-must-haves-panoman.html"&gt;Panoman&lt;/a&gt; which are very wide. So I cannot apply a single size to all pictures - I need to keep the aspect ratio. I decided to write a little bulk image scaler myself.&lt;/p&gt;
&lt;p&gt;The basic image resizer is pretty simple using Java:&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
...
 BufferedImage img=ImageIO.read(new File(fileName));
 if(img==null)
  return;
   
 int w=(int)(img.getWidth()*factor);
 int h=(int)(img.getHeight()*factor);
 String imageType=getFileSuffix(fileName);
 Image scaledImg=img.getScaledInstance(w, h, Image.SCALE_DEFAULT);
 BufferedImage bi=new BufferedImage(w, h, img.getType());
 bi.createGraphics().drawImage(scaledImg, 0, 0,null);
 ImageIO.write(bi, 
  imageType, 
  new File(newFileName));
...
&lt;/pre&gt;
&lt;p&gt;It is not the most efficient way to do this but it is simple and works. To keep aspect ratio is simply a matter of using the new width and the original picture's aspect ratio to calculate the new height:&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
 double ratio=(double)img.getHeight() / img.getWidth();
 int h=(int) (ratio*w);
&lt;/pre&gt;
&lt;p&gt;The full source code is &lt;a href="http://pyp4tg.blu.livefilestore.com/y1pJxUDP-WAOgtLfvfMNglJP2oPqZqX-NjmkRccbF_ukgWzDnUUXS_2h5bkhUPLXcwiXQsv-oHc71lZpXDsKdpAlYV8XytYlw01/PhotoScaler.java?download"&gt;PhotoScaler.java&lt;/a&gt; and &lt;a href="http://pyp4tg.blu.livefilestore.com/y1p5fjDBnlvJCyYATa2S65229IHWdlmvMepVgLcpXp0uq1Y-wobjxO15PwavcLtCXuWT1xof142kEXqGcOGtmslAUszIsmAwLNN/ScaleParameters.java?download"&gt;ScaleParameters.java&lt;/a&gt;. It still needs some refactoring and cleaning up. The usage of the utility:
&lt;pre name="code" class="shell"&gt;
Usage: java com.laws.photo.PhotoScaler directory_name [-f scale_factor] | [-s w
h] | [-k w]
-f scale by factor
-s scale by size, i.e. width and height
-k keep aspect ratio, use w for width and calculate the height
e.g. java PhotoScaler c:\temp\photos -f 0.5
     java PhotoScaler /tmp/photos -s 800 600
     java PhotoScaler c:\temp\photos -k 800

&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4474158352271021848?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4474158352271021848/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4474158352271021848' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4474158352271021848'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4474158352271021848'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/10/bulk-photo-scaler.html' title='Bulk Photo Scaler'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5837167234976239050</id><published>2009-09-22T12:41:00.007+10:00</published><updated>2009-09-22T14:11:51.579+10:00</updated><title type='text'>Confession of a Piano Shopper</title><content type='html'>I have been piano shopping in the last few weeks. During this time I have innundated myself  with all sorts of information from all sources that I could find - dealers, community  forums, pianists and educators. I have been working in the telco industry for many years and  some of which were in the sales process. I found striking similarities between my piano shopping experience and the telco sales process (or perhaps sales in any industry). Many of the behaviour and psychology that I have been critical of also manifested in me and I have no intention of correcting them.

&lt;h2&gt;Wants vs. Needs&lt;/h2&gt;
&lt;p&gt;
The purpose of buying a piano is two-fold: entertainment for me and education for my 7yo  son. I am a self-taught beginner and have no plan of achieving any  certification/qualification in music whatsoever. I just enjoy playing. Chances are that I  will remain a beginner forever. My son is also a beginner but taught 'properly' in a piano  school. He likes playing and needs a proper piano to practise on.
&lt;/p&gt;&lt;p&gt;
So for beginners like us, a new entry-level(110cm - 118cm) acoustic or a digital piano would  be sufficient. However, I don't feel content to have the basic model. I want to have a  'professional' (&gt;120cm) one to start with. Well, my excuse for having a professional model is  that there are two players at home and I don't want to upgrade anytime soon. So I might as  well have the room for potential growth. But does this justify doubling the price?
&lt;/p&gt;
&lt;h2&gt;Bag a Bargain&lt;/h2&gt;
&lt;p&gt;
So I need to squeeze the vendors to drop the price as much as I can. Normally (in  Australia), the RRP of any new piano is inflated by at least 20%. So without any effort, I  can get the price down by 20%. Anything below that I will have to work on it. The easiest way is to have 2 vendors out-bid each other. 
&lt;/p&gt;&lt;p&gt;
But what about the value of the piano - the materials, the labor, the quality, the beauty  and the enjoyment we can get out of it? Are they worth a few thousand dollors? My opion is a resounding 'yes'. A piece of furniture or even a mobile phone can cost thousands of dollors these days.  However, being a buyer, I cannot help exercising my power and try to squeeze that last few  dollors, an adjustable stool or an extra tuning from the vendor. Of course, to negotiate  effectively, I need to equip myself with knowledge about pianos and the piano market.
&lt;/p&gt;
&lt;h2&gt;Knowledge is Power?&lt;/h2&gt;
&lt;p&gt;
Knowing your products definitly helps in negotiation. I have learnt so much about pianos  from all sort of sources in the last few days - information as well as misinformation. I had  narrowed down my choices to Yamaha T121 and Kawai K3. A Yamaha dealer told me that the new  T121 are fully made in Japan, if anyone tells you it's made in Taiwan don't believe them  because the Taiwanese factory had been closed down a few years ago. When I checked online I  did find some comments about the T121 being partially made in Taiwan, but all those comments  are in or before year 2006. Also, you have to take any online information with a grain of salt (the best online info about piano I found so far is &lt;a href="http://www.pianoworld.com/forum/"&gt;Piano World Forum&lt;/a&gt;)  When I visited a Kawai dealer, he told me again that the T121 was made in Taiwan and that is  why its price is lower. He also sited the &lt;a  href='http://www.amazon.com/exec/obidos/ASIN/1929145012/ref=nosim/pianoaccess02-20'&gt;The  Piano Book&lt;/a&gt; (which is an authoritative book on buying pianos) and true enough, the book  said so too! When I checked the date of the book, it was last edited in year 2000. So I know  in this case, who was giving me the true information.
&lt;/p&gt;&lt;p&gt;
Conversely, I quite like the &lt;a href='http://www.kawaius.com/main_links/vertical_09/ABS/absc_up.html'&gt;Millennium III Action&lt;/a&gt; (by using carbon-fibre for certain parts of the action) invented by Kawai, which can increase play speed by up to 16% compared to  traditional wood action. But when coming from a Yamaha dealer's mouth, it totally changed  taste. He argued that 'the instrument needs to breath just like leather shoes  vs. synthetic leather shoes... have you ever seen any musical instrument made from  plastic?... carbon-fibre may be good for making spaceships or boats, but that does not mean  it's appropriate for piano... if plastic is so good, why doesn't Steinway use it...' Looking at input from both sides it is very clear to me that the MIII does have its advantages and the Yamaha dealer's argument does not hold water. Both Yamaha and Kawai are reputable big piano makers. However, Yamaha is no.1 player in the market - 80% of pianos used in musical institutions in Japan are Yamaha. So naturally, as a no. 2 player, Kawai has to work harder by employing new technologies to achieve better results and lower cost. Having said all that, does the 16% faster response mean anything to me? I would say 80% chance 'no' since I will never reach the skills level required to enjoy the benefit; maybe better odds for my son. 
&lt;/p&gt;
&lt;p&gt;
Does all these product knowledge help when choosing a piano? A real player will tell you 'no'. Piano appreciation is very personal. The only ways to pick one are to &lt;a  href='http://www.pianoguide.org/howtobuy.html'&gt;listen, look and play&lt;/a&gt;. All the new  technologies, great designs, country of origin and fine-grained spruce from Alaska will not help if you don't like the sound or touch of the overall product.
&lt;/p&gt;
&lt;h2&gt;Apples and Oranges&lt;/h2&gt;
&lt;p&gt;
Having considered the old and new; the Japanese brands, Korean brands and some German brands I have narrowed down to Kawai K3 and Yamaha T121. The short listing process is extremely painful because many of the pianos that I excluded were very good ones but out of my budget. It is also unfair to directly compare most of the models side by side because they were not made to be at the same level. For example, although all models have the same height, the &lt;a href="http://www.yamaha.com/yamahavgn/CDA/ContentDetail/ModelSeriesDetail.html?CNTID=1454&amp;CTID=201200"&gt;T121&lt;/a&gt;, &lt;a href="http://www.kawaius.com/main_links/vertical_09/k3.html"&gt;K3&lt;/a&gt; and &lt;a href="http://www.yamaha.com/yamahavgn/CDA/ContentDetail/ModelSeriesDetail/0,6373,CNTID%253D1373%2526CTID%253D201200%2526CNTYP%253DPRODUCT,00.html"&gt;U1&lt;/a&gt; are very different products targetting different market segments. The specifications for T121 and U1 on Yamaha's web site show almost identical data, yet U1 is almost double the price of T121 and many players swear that it has better sound. It just does not make sense to claim which one is 'better' especially when it's all about personal experience. 
&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;
With K3 on top of my list and T121 as a backup, I am going to drive a hard bargain after my holiday. Hopefully, I will have a brand new K3 in my living room in a few weeks time.
&lt;/p&gt;
&lt;a href="http://www.zuhalmuzik.com/new/kawai/upright/images/k3.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 342px;" src="http://www.zuhalmuzik.com/new/kawai/upright/images/k3.jpg" border="0" alt="" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5837167234976239050?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5837167234976239050/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5837167234976239050' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5837167234976239050'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5837167234976239050'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/09/confession-of-piano-shopper.html' title='Confession of a Piano Shopper'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7136905065300488071</id><published>2009-08-06T17:26:00.002+10:00</published><updated>2009-08-06T17:48:03.086+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Symbian'/><title type='text'>GPS Navigators on Symbian</title><content type='html'>&lt;p&gt;
As soon as &lt;a href="http://www.nokia.com.au/find-products/all-phones/nokia-5800-xpressmusic1/specifications"&gt;Nokia XpressMusic 5800 &lt;/a&gt;came out in Australia, my wife conveniently lost her &lt;a href="http://www.htc.com/au/product/838pro/overview.html"&gt;HTC touch smartphone&lt;/a&gt;. So I bought the XM 5800 for her from Bangkok (and it turned out to be genuine &lt;img src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif'/&gt;). Then on the same day she conveniently found her HTC in her friend's car...
&lt;/p&gt;
&lt;p&gt;So I was charged to install all the must-have software onto the new phone. Since both XM 5800 and my old N95 support GPS, a GPS navigator became the top of the list. I have tried &lt;a href="http://www8.garmin.com/support/download_details.jsp?id=3493"&gt;Garmine Mobile XT&lt;/a&gt;, &lt;a href="http://www.66.com/route66/index.php?cid=UK&amp;act=1&amp;catid=70"&gt;Route 66&lt;/a&gt;, &lt;a href="http://www.allaboutsymbian.com/reviews/item/TomTom_NAVIGATOR_6.php"&gt;TomTom &lt;/a&gt;and Nokia Map using my N95. I quickly dismissed Garmin since it refused to start the trial period claiming some error requesting the server over the internet (and I am sure my wifi setting was correct and it did get through the internet to attempt to connect to the Garmin server).
&lt;/p&gt;
&lt;p&gt;I have been using R66 Mobile 7 and then 8 for a while until I successfully installed TomTom v6.02 on my N95. The R66 maps simply don't look good comparing to TT and Nokia Map.
&lt;/p&gt;
&lt;p&gt;So now I am using TT and Nokia Ovi Map (v3.01) at the same time.
&lt;/p&gt;
&lt;p&gt;Comparing to Nokia Map, TT is better at navigation. As soon as you type a letter, it intelligently prompts a list of names. When I typed Dolls Point, it automatically points to the beach which is exactly what I wanted. The lane guidance and camera alert work perfectly.
&lt;/p&gt;
&lt;p&gt;The disadvantage of TT comparing to Nokia Map is in its map browsing capability - perhaps rightly so, considering the fact that TT is a navigator, not a map system.
&lt;/p&gt;
&lt;p&gt;Nokia Map shines in its usability - you can easily download and configure maps and voice packs in different languages. Its maps are quite pretty and detailed. Here is a screenshot of Nokia Map showing where I am stranded (due to mechanical failure of Royal Brunei Airline flight).&lt;/p&gt;
&lt;a href="http://dtt0ua.blu.livefilestore.com/y1pk9iWLAsdlVNxgPgDNOityVKh33sSTKuJVAR5og0Qors4GMozRUp-92O8uTZ12g6FgVMaM7SeAtMAxhNa2sCRC40rP3R79eIS/Esplanade.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://dtt0ua.blu.livefilestore.com/y1pk9iWLAsdlVNxgPgDNOityVKh33sSTKuJVAR5og0Qors4GMozRUp-92O8uTZ12g6FgVMaM7SeAtMAxhNa2sCRC40rP3R79eIS/Esplanade.jpg" border="0" alt="" /&gt;&lt;/a&gt;
&lt;p&gt;However, the version that I am using does not seem to include camera alert or lane guidance.&lt;/p&gt;
&lt;p&gt;When it comes to the XM 5800, there is not much choice because it is running S60 v5 and not equipped with any keyboard. The only choice on XM 5800 for me is Nokia Map.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7136905065300488071?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7136905065300488071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7136905065300488071' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7136905065300488071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7136905065300488071'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/08/gps-navigators-on-symbian.html' title='GPS Navigators on Symbian'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-3692690285339717699</id><published>2009-07-26T14:47:00.015+10:00</published><updated>2010-03-18T12:41:17.766+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><title type='text'>Using XML/SWF Gauge</title><content type='html'>&lt;p&gt;I have been looking for flash widgets to show data in a dashboard. The &lt;a href="http://www.maani.us/gauge/index.php"&gt;XML/SWF Gauge &lt;/a&gt;has become my best choice so far. It only requires one &lt;code&gt;gauge.swf&lt;/code&gt; file which takes in a XML configuration file to instruct it what and how to draw the gauge. I used it to implement a Radar View as part of my dashboard.&lt;/p&gt;
&lt;object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" 
 WIDTH="380"
 HEIGHT="315"
 id="gauge"&gt;
&lt;param name='movie' value=http://public.blu.livefilestore.com/y1pYqeqk-Fpk9gLlPfhVU8rcc1UK5AjMEq8xLmATHWakiWBWaE_td3zsB2va9XRLRLzbU4qwTvH19WKcFG6u2oOyA/gauge.swf?xml_source=http://public.blu.livefilestore.com/y1pvlhPsffSZmZtmoZxzIRVSu19BNB_1PI2ajykLk05u79NA_ywZY8dwLCQ97LcNglmytGqy_3ovccJq-8yyAtMug/Radar.xml'&gt; &lt;/param&gt;
&lt;param name='quality' value='high'&gt; &lt;/param&gt;
&lt;param name='bgcolor' value='#000000'&gt; &lt;/param&gt;
&lt;param name='allowScriptAccess' value='sameDomain'&gt; &lt;/param&gt;

&lt;embed src='http://public.blu.livefilestore.com/y1pG-kaV6YX701H6eSktJtpYbeWMUZn8VObXV_hW12t5cHR4rgdGzwevR6nxseMqEjVprz251z2ps4Wbvt4jevX3g/gauge.swf?xml_source=http://public.blu.livefilestore.com/y1p6EpPoceBMWH5c5BKfmZVYIKUHixQhfDnsYyrsNfIgNGUKPmqZUWf22THpcl8EmceUnJld3_OLQWb20lNERyPtA/Radar.xml'
 quality="high" 
 bgcolor="#000000" 
 WIDTH="380" 
 HEIGHT="315" 
 NAME="gauge" 
 allowScriptAccess="sameDomain" 
 swLiveConnect="true" 
 TYPE="application/x-shockwave-flash" 
 PLUGINSPAGE="http://www.macromedia.com/go/getflashplayer"&gt;
&lt;/embed&gt;
&lt;/object&gt;
&lt;P&gt;
I like the simplicity of the XML/SWF Gauge when using it. Yet it generates versatile and visually pleasing results. It also supports &lt;a href="http://www.maani.us/gauge/index.php?menu=Tutorial&amp;submenu=Gauge_Source"&gt;dynamic data &lt;/a&gt;display and scheduled &lt;a href="http://www.maani.us/gauge/index.php?menu=Reference&amp;submenu=update"&gt;update/refresh&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;Since I used it in a ZK web application, naturally I used ZK to generate the input XML file dynamically. By default XML files are not passed to the ZK Loader to handle. To force it to be passed to ZK Loader I had to add a servlet mapping in &lt;code&gt;web.xml&lt;/code&gt; file:
&lt;/p&gt;
&lt;pre name="code" class="xml"&gt;
&lt;servlet-mapping&gt;
   &lt;servlet-name&gt;zkLoader&lt;/servlet-name&gt;
   &lt;url-pattern&gt;*.xml&lt;/url-pattern&gt;
&lt;/servlet-mapping&gt;
&lt;/pre&gt;
I can use the same &lt;code&gt;gauge.swf&lt;/code&gt; to generate all sorts of views - radar, meter, dial ... all I needed is to generate the appropriate XML file to feed the &lt;code&gt;gauge.swf&lt;/code&gt; as shown in the following ZUL file segment. 
&lt;pre name="code" class="bash"&gt;
...
&lt;flash id="gaugeFlash" style="overflow:auto"
 src="/xmlSwfGauge/gauge.swf?xml_source=xmlSwfGauge/${view.type}.xml%3Fvid%3D${view.name}" 
 width="${d}px" height="${d}px"&gt;
&lt;attribute name="onMetricChanged"&gt;
        // ...
&lt;/attribute&gt;
&lt;attribute name="onRefreshGaugeView"&gt;
 if (view.getName().equals(event.getData())) {
  // recalculate view attributes...
  // refresh:
  gaugeFlash.invalidate();
 }
&lt;/attribute&gt;
&lt;/flash&gt;
&lt;/pre&gt;
I do have the following complaints on XML/SWF Gauge:
&lt;ol&gt;
&lt;li&gt;It does not support concurrent/nested animation.&lt;/li&gt;
&lt;li&gt;It does not support event-driven updates (vs. scheduled updates).&lt;/li&gt;
&lt;/ol&gt;
If you have come across any good flash gauge packages, please leave a comment.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-3692690285339717699?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/3692690285339717699/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=3692690285339717699' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3692690285339717699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3692690285339717699'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/07/using-xmlswf-gauge_7432.html' title='Using XML/SWF Gauge'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1989383916211391816</id><published>2009-07-01T23:05:00.003+10:00</published><updated>2009-07-05T20:16:29.360+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Of Twitter, Clouds and Google Goo</title><content type='html'>&lt;p&gt;I have always thought Twitter is a time waster. However, I have also noticed that there are many companies and individuals using it as a marketing channel to reach the global mass, not to mention the propaganda tool as witnessed in the recent Iranian demonstrations. So instead of subscribing to RSS feeds, following tweets is now the "in" thing to do.&lt;/p&gt;
&lt;p&gt;Using a social network for commercial and political gains is not new. We have seen it on &lt;a href="http://www.facebook.com/profile.php?id=1092201851&amp;hiq=romen%2Claw"&gt;Facebook &lt;/a&gt;and &lt;a href="http://secondlife.com/"&gt;2nd Life&lt;/a&gt;. However, to &lt;a href="http://streambase.typepad.com/streambase_stream_process/2009/06/trading-on-twitter-opportunity-danger-or-folly.html"&gt;use tweets as an input for trading decision&lt;/a&gt; sounds dubious. Much of the twitter tweets are just noise and even garbage.&lt;/p&gt;
&lt;p&gt;Today, out of curiosity I subscribed to &lt;a href="http://twitter.com/romenlaw"&gt;Twitter&lt;/a&gt;. Within minutes of me opening my new Twitter account, I already got a follower. To be honest, I was pleasantly surprised and even flattered. Yet when I checked, it turned out to be a prostitute or cyber-pimp pushing some porn site &lt;img src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif'/&gt;. So to make use of tweets for trading, the system will have to identify which users to follow and filter out the fake, malicious and manipulating users - much like virus scanners rely on their virus database. This is a time-consuming and even labor intensive heuristic process relying on large volume of data. Then it has to filter and analyse the millions of messages per day to extract the useful information.&lt;/p&gt;
&lt;p&gt;Let's put aside the ethical issues behind the practice of 'trading on rumors'. To say that a machine can determine market sentiment by reading tweets is at best an overstatement. Even human beings have trouble reading the sentiment in cyberspace, and that is why people have to add all sorts of smileys, emoticons and internet etiquette to assist the reader of the message. Also, the same words can have drastically different intentions and reactions based on different cultural, religional and circumstantial backgrounds. The idea of trawling through the internet to extract marketing information is not new. People have been attempting it on RSS feeds, newsgroups, user forums, etc. for a few years now. However, they are very focused/targetted on certain types of contents and are not of realtime nature -certainly not as ambitious as making trading decisions in real-time.&lt;/P&gt;
&lt;p&gt;To apply complex fuzzy logic algorithms on large amount of (current as well as historical statistical) data is very CPU-, memory- and data-intensive. Such jobs are best suited for cloud-computing, which many big players are pushing - Sun, Microsoft, Amazon and Google. A couple of days ago I stumbled upon some cloud-computing PR articles and interviews and found this one - &lt;a href="http://www.infoq.com/cn/interviews/guxuemei-cloudcomputing"&gt;谷雪梅谈云计算&lt;/a&gt;. I realised that the Google chief engineer interviewed in that video was my high school classmate. We affectionately called her 'Goo' back then. I guess now I have to call her the 'Goo of Google'&lt;img src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif'/&gt;. In that interview, a question was asked about how Google makes money. Well, I believe in 'Knowledge is power' and more so in the information age and that is how Google makes money. Comparing to the new comers (e.g. &lt;a href="http://www.bing.com/"&gt;Bing&lt;/a&gt;, &lt;a href="http://blog.wolfram.com/2009/03/05/wolframalpha-is-coming/"&gt;WolframAlpha&lt;/a&gt;) Google's search algorithm is quite lazy and unsophisticated - it relies on external links or the more you pay the higher the position in the list, yet it has accumulated vast amount of historical data and trained its systems to give better results. So Google has now taken the steps further to sell the infrastructure services and technologies such as GWT, cloud computing, Chrome and Google Wave. It seems Google has better things to do than to recycle the Twitter garbage - for now.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1989383916211391816?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1989383916211391816/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1989383916211391816' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1989383916211391816'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1989383916211391816'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/07/of-twitter-clouds-and-google-goo.html' title='Of Twitter, Clouds and Google Goo'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-3499223317089682407</id><published>2009-06-22T13:16:00.009+10:00</published><updated>2009-06-22T13:51:30.265+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Web Service'/><category scheme='http://www.blogger.com/atom/ns#' term='WCF'/><title type='text'>JAXB Custom Data Binding</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;In a &lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;previous post &lt;/a&gt;I experimented consuming WCF web services using various Java WS frameworks and tools. As pointed out by Alex, the one that I missed out was &lt;code&gt;wsimport&lt;/code&gt; which is bundled as part of JSE6.
&lt;p&gt;
Like many other tools, it supports both Ant task and command line interface (CLI). The CLI for wsimport is quite simple - in my case I generated the source code and client stub library like so:&lt;/p&gt;
&lt;pre class='groovy' name='code'&gt;
D:\Program Files\Java\jdk1.6.0_11\bin&gt;wsimport -d /temp/generated -s /temp/gensrc -keep http://localhost/PromoService.svc?wsdl
parsing WSDL...


generating code...

D:\Program Files\Java\jdk1.6.0_11\bin&gt;
&lt;/pre&gt;
All the rest is similar to the results of IntelliJ shown in my &lt;a href='http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html'&gt;previous post&lt;/a&gt;. There are two problems with the generated &lt;code&gt;PromoInfo.java&lt;/code&gt; which is a data/value object:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt; fields are generated as &lt;code&gt;JAXBElement&amp;lt;String&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dateTime&lt;/code&gt; fields are generated as &lt;code&gt;XMLGregorianCalendar&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
I want to use core java data types on the data objects so that they can be easily integrated with other frameworks without having to do conversion. Examining my schema (on &lt;code&gt;http://localhost/PromoService.svc?xsd=xsd2&lt;/code&gt;) the PromoInfo complex type is defined as
&lt;pre class='xml' name='code'&gt;
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/svdemo" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.datacontract.org/2004/07/svdemo"&gt;
  &lt;xs:complexType name="PromoInfo"&gt;
    &lt;xs:sequence&gt;
      &lt;xs:element minOccurs="0" name="PromoDateTime" type="xs:dateTime"/&gt;
      &lt;xs:element minOccurs="0" name="PromoDescription" nillable="true" type="xs:string"/&gt;
      &lt;xs:element minOccurs="0" name="PromoName" nillable="true" type="xs:string"/&gt;
      &lt;xs:element minOccurs="0" name="PromoVenue" nillable="true" type="xs:string"/&gt;
    &lt;/xs:sequence&gt;
  &lt;/xs:complexType&gt;
&lt;xs:element name="PromoInfo" nillable="true" type="tns:PromoInfo"/&gt;
&lt;/xs:schema&gt;
&lt;/pre&gt;
It is obvious that the &lt;code&gt;xs:string&lt;/code&gt; and &lt;code&gt;xs:dateTime&lt;/code&gt; were not converted into the desired java types. To solve my problems I specified &lt;a href="http://java.sun.com/javaee/5/docs/tutorial/doc/bnbbf.html"&gt;customised JAXB binding&lt;/a&gt; rules in an external file - &lt;code&gt;custombinding.xml&lt;/code&gt; like so
&lt;pre class='brush: xml; highlight: [2,3];' name='code'&gt;
&lt;jxb:bindings version="1.0"
    xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" &gt;
 &lt;jxb:globalBindings generateElementProperty="false" &gt;
     &lt;jxb:javaType name="java.util.Date" xmlType="xs:dateTime" /&gt;
 &lt;/jxb:globalBindings&gt;
&lt;/jxb:bindings&gt;
&lt;/pre&gt;
The attribute &lt;code&gt;generateElementProperty="false"&lt;/code&gt; on line 2 tells &lt;code&gt;wsimport&lt;/code&gt; not to generate &lt;code&gt;JAXBElement&lt;/code&gt; but to generate native java data types instead.
&lt;p&gt;The &lt;code&gt;javaType&lt;/code&gt; element on line 3 defines the binding between &lt;code&gt;"xs:dateTime"&lt;/code&gt; and &lt;code&gt;"java.util.Date"&lt;/code&gt; because by default xml &lt;code&gt;dateTime&lt;/code&gt; binds to &lt;code&gt;javax.xml.datatype.XMLGregorianCalendar&lt;/code&gt; as shown &lt;a href='http://java.sun.com/javaee/5/docs/tutorial/doc/bnazq.html'&gt;here&lt;/a&gt;.
&lt;/p&gt;
Once the binding is defined, rerunning the &lt;code&gt;wsimport&lt;/code&gt; tool with the &lt;code&gt;-b&lt;/code&gt; switch will produce the desired output:
&lt;pre class='ruby' name='code'&gt;
D:\Program Files\Java\jdk1.6.0_11\bin&gt;wsimport -d /temp/generated -s /temp/gensrc -b /temp/custombinding.xml http://localhost/PromoService.svc?wsdl
parsing WSDL...


generating code...
Note: D:\temp\gensrc\org\w3\_2001\xmlschema\Adapter1.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

&lt;/pre&gt;
This time the generated &lt;code&gt;PromoInfo.java&lt;/code&gt; looks much better:
&lt;pre class='java' name='code'&gt;
...
public class PromoInfo {

    @XmlElement(name = "PromoDateTime", type = String.class)
    @XmlJavaTypeAdapter(Adapter1 .class)
    @XmlSchemaType(name = "dateTime")
    protected Date promoDateTime;
    @XmlElement(name = "PromoDescription", nillable = true)
    protected String promoDescription;
    @XmlElement(name = "PromoName", nillable = true)
    protected String promoName;
    @XmlElement(name = "PromoVenue", nillable = true)
    protected String promoVenue;
...
&lt;/pre&gt;&lt;br/&gt;&lt;br/&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-3499223317089682407?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/3499223317089682407/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=3499223317089682407' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3499223317089682407'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3499223317089682407'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/06/jaxb-custom-data-binding.html' title='JAXB Custom Data Binding'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5946764583385169009</id><published>2009-06-18T11:33:00.002+10:00</published><updated>2009-06-21T16:01:41.641+10:00</updated><title type='text'>A Racist Job Market</title><content type='html'>&lt;p&gt;My friend forwarded me an interesting article today - &lt;a href='http://www.abc.net.au/news/stories/2009/06/17/2601052.htm'&gt;Ethnic names hinder job seeking: report&lt;/a&gt;. I have also seen similar reports on Channel 7 two days in a row.&lt;/p&gt;
&lt;p&gt;The result of the research shows that job applicants with Anglo-Saxon names received more calls than those with Indigenous, Chinese, Middle Eastern or Italian names. Whether the business owners/employment agencies/HR people are consciously racist or not is debatable. However, it is proven that collectively the job market in Australia is racist.&lt;/p&gt;
&lt;p&gt;The rationale behind this phenomenon is complex and multifaceted. Let's assume people are not intentionally or consciously racist for a while. There could be several reasons behind it:
&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Typecasting &lt;/i&gt;- Once someone sees a 'foreign' name, he/she automatically assign a profile to it. Of course, there could be good and bad aspects of this profile depending on the reviewer's personal experiences and belief. The biggest disadvantage of such profiles for foreign names is perhaps the assumption that they do not speak English well. Hence we see many job ads have requirements like "excellent communication skills" to deter non-English speakers from even applying. True, Australia is a country of immigrants and there are many new immigrants who cannot speak English well. However, majority of them are quick and keen learners, especially those who are actively seeking jobs. And let's not forget the vast number of 2nd-, 3rd-... generations of immigrants who identify themselves as true-blue Aussies.
&lt;/p&gt;
&lt;p&gt;&lt;i&gt;"Not Made Here" syndrome&lt;/i&gt; - People are more comfortable with what they are already familiar with. A foreign name automatically triggers off a level of fear and set off the defense mechanism subconsciously (or unconsciously if you prefer Freudism). Many people tend to prefer interacting with people with similar backgrounds and interests forgetting the advantages of diversity, especially in a work environment. There are a large number of small businesses in Australia which have only a handful of employees in the workplace. Hiring people with the same traits/profile that everyone else is comfortable with becomes an important criterion.
&lt;/p&gt;
&lt;p&gt;&lt;i&gt;Media &lt;/i&gt;- The mass media is the most powerful brainwashing machine in the world and often, by criticising their countries of origin the media cast negative lights on the ethnic minorities who live in Australia. 
&lt;/p&gt;
&lt;p&gt;Although we have been told not to judge a book by its cover, people cannot easily shake off the racial prejudice that have been intrinsically wired into our brain and &lt;a href='http://www.associatedcontent.com/article/8712/does_the_heart_have_a_memory.html?cat=5'&gt;heart &lt;/a&gt;as a result of millions of years of evolution. To break down the racial barrier, we have to actively and consciously broaden our horizon; seek more interactions with all walks of life; treat individuals as individuals; do upon others that which you will have them do onto you.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5946764583385169009?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5946764583385169009/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5946764583385169009' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5946764583385169009'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5946764583385169009'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/06/racist-job-market.html' title='A Racist Job Market'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1512792433492272896</id><published>2009-06-11T11:40:00.008+10:00</published><updated>2009-06-22T13:55:27.771+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><category scheme='http://www.blogger.com/atom/ns#' term='Chrome'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>One Year On</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;I started this blog one year ago in June 2008. I have been using &lt;a href='http://www.google.com/analytics/'&gt;Google Analytics&lt;/a&gt; to track the site. Here are some visitor (mostly from IT and development communities) trends collected in the last year or so.
&lt;p&gt;
&lt;b&gt;Browsers used:&lt;/b&gt;
&lt;/p&gt;
&lt;img src='http://rzlqoa.blu.livefilestore.com/y1pqMK8fOO__8yNjO2ZAEVoLVYuAA30SfL_jyqfPZXE6GR1bUc0cP3Hfm_vQn3re_omhQqOGbYu9iwU1-4mEiwJNpB65H9Hsw5A/trend_browsers.png'/&gt;
&lt;p&gt;
&lt;b&gt;OS used:&lt;/b&gt;
&lt;/p&gt;
&lt;img src='http://rzlqoa.blu.livefilestore.com/y1p9FAwMsewMxBT6Bz1SDMPQxzuxn8vgAJgg5XM3d4Y7Kw9iloOCGWI3r0TylrPwkUlMzcCtrkHbfTktlRJE3N3OGU225DKjyQK/trend_os.png'/&gt;
&lt;p&gt;
&lt;b&gt;Browser + OS used:&lt;/b&gt;
&lt;/p&gt;
&lt;img src='http://rzlqoa.blu.livefilestore.com/y1pnsuTahtveEeLlXLhGhrOqaNCX63zYeVNt8m46VUcvhGF9hGokpSIFaeKWuXI5WR964et5A4anaVlp0CsKR1UJOqDiXa3_P7Y/trend_browseros.png'/&gt;
&lt;p&gt;
&lt;b&gt;Java support:&lt;/b&gt;
&lt;/p&gt;
&lt;img src='http://rzlqoa.blu.livefilestore.com/y1p9FAwMsewMxCuzuDZcyYhNThVN76IUWgJxLpK2L5396a4YRc1awC65-0IJTfyjXh4ouQXHbwNFbe14U7fQ0uHhUbYqbHIigM1/trend_java.png'/&gt;
&lt;p&gt;
As of June 2009 the top 5 visited posts on this blog site are:
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;Consuming WCF Web Service Using Java Client&lt;/a&gt;, &lt;em&gt;July 2008&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-from-android.html"&gt;Consuming Web Services from Android&lt;/a&gt;, &lt;em&gt;August 2008&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2009/05/ie8-problem-with-outlook-web-access.html"&gt;IE8 + Outlook Web Access = Problems&lt;/a&gt;, &lt;em&gt;May 2009&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/07/wpf-splash-with-progressbar.html"&gt;WPF Splash with ProgressBar&lt;/a&gt;, &lt;em&gt;July 2008&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/09/how-to-add-unicode-fonts-to-n95.html"&gt;How To Add Unicode Fonts to N95&lt;/a&gt;, &lt;em&gt;September 2008&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;
&lt;p&gt;
There are a couple of trends worth noticing:
&lt;ol&gt;
&lt;li&gt;Firefox has a bigger market share than IE among the technical users and rightly so. It will be interesting to see how Chrome will measure up against the top two. Chrome has certainly great momentum considering it has only been launched 9 months ago and already in 3rd place. &lt;/li&gt;
&lt;li&gt;Java support is not as ubiquitous as I first thought. I wouldn't develop my next RIA business application on JavaFX any time soon. So right now I will stick to ZK (and maybe SmartSWT) and wait for HTML5 to take over.&lt;/li&gt;
&lt;li&gt;It only took less than a month for the &lt;a href="http://romenlaw.blogspot.com/2009/05/ie8-problem-with-outlook-web-access.html"&gt;IE8 + Outlook Web Access = Problems&lt;/a&gt; post to claim the no. 3 spot. It just shows how bad IE8 is.&lt;/li&gt;
&lt;li&gt;The post on number 5 spot has far more comments than the rest. It proves once again that non-technical people are far more social &lt;img src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif'/&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1512792433492272896?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1512792433492272896/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1512792433492272896' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1512792433492272896'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1512792433492272896'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/06/one-year-on.html' title='One Year On'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5360304412958100393</id><published>2009-05-22T10:44:00.001+10:00</published><updated>2009-05-22T10:44:02.463+10:00</updated><title type='text'>IE8 + AVG = Problem?</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;My IE8 fails to connect to any site from time to time. This happens sporadically and on certain new tabs only. After trawling through the suggestions in &lt;a href='http://groups.google.com/group/microsoft.public.internetexplorer.general/browse_thread/thread/fc8ec7c712f87ec0/20ec6e3463d4bd39?lnk=raot&amp;amp;pli=1'&gt;this discussion group&lt;/a&gt;, I found that the following solutions from fufufufu worked for me.
&lt;ol&gt;
&lt;li&gt;Work around: if one tab does not connect, try open another one. Some tabs will eventually work.&lt;/li&gt;
&lt;li&gt;Solution: disable my AVG Anti-Virus Free (v8.5)'s Link Scanner feature (done in AVG's GUI client)&lt;/li&gt;
&lt;/ol&gt;
Interestingly, none of these problems with IE had ever occurred to my trusted FireFox.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5360304412958100393?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5360304412958100393/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5360304412958100393' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5360304412958100393'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5360304412958100393'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/05/ie8-avg-problem.html' title='IE8 + AVG = Problem?'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4961546337188078747</id><published>2009-05-19T10:02:00.001+10:00</published><updated>2009-05-28T10:05:39.941+10:00</updated><title type='text'>IE8 + Outlook Web Access = Problems</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;A couple of days ago I upgraded my old IE6 to IE8 because IE6 refused to display one of my &lt;a href='http://zkoss.org'&gt;ZK&lt;/a&gt; pages and spat out an error message box.&lt;/p&gt;
&lt;p&gt;As I had to use Outlook Web Access (OWA) daily (which is the only reason why I still have IE on my machine), I ran into trouble straight away after the upgrade:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Any editing screens in OWA - e.g. editing a mail was incredibly slow. This was fixed by clicking the &lt;b&gt;Options &lt;/b&gt;view in OWA and scroll down to &lt;b&gt;E-mail Security&lt;/b&gt; section and click &lt;code&gt;Re-install&lt;/code&gt; button. This installed the latest S/MIME ActiveX.&lt;/li&gt;
&lt;li&gt;When downloading Office 2007 file types (docx, xlsx, pptx, etc.), OWA attempts to download them as .zip files. This was fixed by adding my OWA web site URL to the Trusted sites list in IE8 (Internet Options -&amp;gt; Security -&amp;gt; Trusted sites) as suggested &lt;a href='http://jdbausch.blogspot.com/2009/05/internet-explorer-8-with-outlook-web.html'&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Cannot send or reply any more! - After working for a couple of days, OWA played up again: when replying mail I got Error: ‎&lt;i&gt;(0x8000ffff)‎: Catastrophic failure&lt;/i&gt;; when sending new mail, I got error messagebox saying access denied... Digging through Google, others have experienced the same problem but no solutions. :( The workaround is to uninstall the OWA S/MIME component (as shown in point 1 above)!
&lt;/li&gt;
&lt;/ol&gt;
Now I know what makes dogs run around and around chasing their own tails - it's their breakfast!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4961546337188078747?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4961546337188078747/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4961546337188078747' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4961546337188078747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4961546337188078747'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/05/ie8-problem-with-outlook-web-access.html' title='IE8 + Outlook Web Access = Problems'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5356170870115216696</id><published>2009-05-14T22:12:00.007+10:00</published><updated>2009-05-15T11:06:25.014+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><title type='text'>Server Push + Event in ZK</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;I recently implemented a dashboard type of web application in &lt;a href='http://zkoss.org/'&gt;ZK&lt;/a&gt; to display a collection of &lt;a href='http://en.wikipedia.org/wiki/Key_performance_indicator'&gt;KPI &lt;/a&gt;metrics data using various views, for example
&lt;ol&gt;
&lt;li&gt;Dashboard view - by using some flash dial/meter widgets&lt;/li&gt;
&lt;li&gt;Tree view - see screenshot below&lt;/li&gt;
&lt;li&gt;Google map view - showing geographical metrics on the map&lt;/li&gt;
&lt;/ol&gt;
&lt;img src='http://dtt0ua.blu.livefilestore.com/y1pQzsPGfOcUlpIfHBMUdXVQdVx_IwdSXlNCpzZ2fIf3EWYPs9CVIFHyUP-iFOTEUAFWqLhaAcPV9wNuhoSgYutbPvJdU5x_dC5/omc.png'/&gt;
&lt;p&gt;
I want to use ZK's server push feature to dynamically update the views only when the corresponding metric value has been changed and detected from the back end/server side. The examples that are available from &lt;a href='http://docs.zkoss.org/wiki/Documentation'&gt;ZK small talks&lt;/a&gt; invariably pass the widget(s) to be updated to the worker thread and have them updated by the server side. This approach does not quite work in my application for the following reasons:
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The various views (see screenshot above) are opened/closed by the user dynamically. Therefore, I don't know which widgets are visible and should be updated;&lt;/li&gt;
&lt;li&gt;There are too many widgets to be updated - there could be dozens or hundreds of metrics displayed in the view. So one approach could be to pass the whole view to the server side and have it figure out which widgets to update. I feel that the server side shouldn't be bothered with such a responsibility and the shear number/volume of the widgets involved in the view could render this infeasible.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
So I took an alternative approach by combining the server push and event posting that are available in ZK framework.
&lt;/p&gt;
&lt;p&gt;
The main page (&lt;code&gt;main.zul&lt;/code&gt; file) contains the code to turn on and off server push as part of its zscript:
&lt;/p&gt;
&lt;pre class='java' name='code'&gt;
void startServerPush() {
 if(!desktop.isServerPushEnabled()){
     desktop.enableServerPush(true);
 }
 MetricsUpdateThread mut = new MetricsUpdateThread(user, desktop);
 mut.start();
}

void endServerPush() {
 if(desktop.isServerPushEnabled()){
     desktop.enableServerPush(false);
 }
}
&lt;/pre&gt;
The &lt;code&gt;MetricsUpdateThread&lt;/code&gt; class is shown below:
&lt;pre class='brush: java; highlight: [14];'&gt;
public class MetricsUpdateThread extends Thread {
 private static final int DELAY_TIME=2000; // 2 second.
 private Desktop _desktop;
 private boolean _ceased;
 
 public MetricsUpdateThread(Desktop desktop) {
  this._desktop=desktop;
 }
 public void updateChangedMetrics() {
  HashSet&amp;lt;Metric&amp;gt; metrics=new HashSet&amp;lt;Metric&amp;gt;();
  // find all changed metrics and put them in the metrics Set
  ...
  if(metrics.size()&gt;0)
    Events.postEvent(new Event("onMetricChanged", null, metrics));
 }
 
 public void run() {
  if (!_desktop.isServerPushEnabled())
   _desktop.enableServerPush(true);
  try {
   while (!_ceased) {
    Executions.activate(_desktop);
    try {
     updateChangedMetrics();
    } finally {
     Executions.deactivate(_desktop);
    }
    Threads.sleep(DELAY_TIME); // Update delay time
   }
  } catch (DesktopUnavailableException due) {
   //System.out.println("Browser exited.");
  } catch (InterruptedException ex) {
   //System.out.println("Server push interrupted.");
  } catch (IllegalStateException ie) {
   //System.out.println("Server push ends.");
  } finally {
   if (_desktop.isServerPushEnabled()) {
    _desktop.enableServerPush(false);
    //System.out.println("Server push disabled.");
   }
  }
 }
}

&lt;/pre&gt;
Notice the &lt;code&gt;Events.postEvent()&lt;/code&gt; in the &lt;code&gt;updateChangedMetrics()&lt;/code&gt; method, which broadcasts an event if there are any changed metrics. These events are then handled by the corresponding view's zscript. For example, the treeview above has the event handler at its root component like so:
&lt;pre class='xml' name='code'&gt;
...
&lt;window height='100%' border='none' id='win'&gt;
&lt;attribute name='onMetricChanged'&gt;
 metrics = event.getData();
 for(Metric metric : metrics) {
  tcMovement = win.getFellowIfAny(metric.getId()+"_move");
  if(tcMovement!=null) {
   tcMovement.setImage(Util.constructMovementImage(metric));
    
   tcValue = win.getFellow(metric.getId()+"_value");
   tcValue.setLabel(metric.getValueAsString(metric.getCurrentValue()));
  }
 }
&lt;/attribute&gt;
...
&lt;/window&gt;
...
&lt;/pre&gt;
This approach of combining Server Push and Event broadcasting achieves the effect that I wanted. However, I can't help feeling that it is a bit complicated. So I wonder whether there is a better, simpler or more standard approach to achieve the same user experience in ZK.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5356170870115216696?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5356170870115216696/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5356170870115216696' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5356170870115216696'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5356170870115216696'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/05/server-push-event-in-zk.html' title='Server Push + Event in ZK'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1101600594018552935</id><published>2009-04-26T22:43:00.007+10:00</published><updated>2009-04-28T22:15:16.162+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><title type='text'>Master-Detail View in ZK</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;In my latest ZK application, I implemented a typical master-detail view: a split bar in the middle; a tree view on the left and a table/grid on the right. Whenever an item in the treeview is clicked, the grid on the right-hand-side is updated to display the details of the clicked treeview item. The only thing 'special' about this application is that I factored the master (treeview) and details (grid) views as separate files and the main window/page uses &lt;code&gt;&amp;lt;include&amp;gt;&lt;/code&gt; to put them together:
&lt;pre class='xml' name='code'&gt;
&amp;lt;?page title="" contentType="text/html;charset=UTF-8"?&amp;gt;
&amp;lt;zk&amp;gt;
&amp;lt;window border="none" width="100%" height="100%"&amp;gt;
 &amp;lt;hbox spacing="0" width="100%" height="100%"&amp;gt;
  &amp;lt;include src="analytical.zul"/&amp;gt;
  &amp;lt;splitter collapse="before"/&amp;gt;
  &amp;lt;include src="metricDetails.zul"/&amp;gt;
 &amp;lt;/hbox&amp;gt;
&amp;lt;/window&amp;gt;
&amp;lt;/zk&amp;gt;
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;include&amp;gt;&lt;/code&gt; complicates things slightly: the master view which is firing the event has no visibility of who is going to handle the event. Therefore, the event's &lt;code&gt;target&lt;/code&gt; field is set to null so that the event is broadcast to all root-level components on the page - including the included details view.
&lt;/p&gt;
&lt;p&gt;The event sending code is shown below. Note that the treeview of the master view is built using model and renderers and the event sending code is embedded in the renderer.&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
public class MetricTreeitemRenderer implements TreeitemRenderer {

 @Override
 public void render(Treeitem item, Object data) throws Exception {
  SimpleTreeNode t = (SimpleTreeNode)data;
  Metric metric = (Metric)t.getData();
  ... // construct Treecells
  Treerow tr = null;
  ... // construct the Treerow
  
  final Event e=new Event("onMetricClick", null, metric);
  tr.addEventListener(Events.ON_CLICK, new EventListener(){

   @Override
   public void onEvent(Event arg0) throws Exception {
    Events.postEvent(e);
   }
   
  });
 }
}
&lt;/pre&gt;
The event handling side is part of the included details view file:
&lt;pre name="code" class="xml"&gt;
&amp;lt;?page title="Metric Details" contentType="text/html;charset=UTF-8"?&amp;gt;
&amp;lt;?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?&amp;gt;
&amp;lt;zk&amp;gt;
&amp;lt;window id="win" title="Metric Details" border="none" width="100%" height="100%"&amp;gt;
&amp;lt;attribute name="onMetricClick"&amp;gt;
... handle the event by populating the grid 
&amp;lt;/attribute&amp;gt;
&amp;lt;grid id="grid" vflex="true" width="100%" height="100%"&amp;gt;

  &amp;lt;columns sizable="true"&amp;gt;
   &amp;lt;column label=""/&amp;gt;
   &amp;lt;column label=""/&amp;gt;
  &amp;lt;/columns&amp;gt;
  &amp;lt;rows&amp;gt;
...
  &amp;lt;/rows&amp;gt;
 &amp;lt;/grid&amp;gt;
&amp;lt;/window&amp;gt;
&amp;lt;/zk&amp;gt;
&lt;/pre&gt;
Note that even though the &lt;code&gt;&amp;lt;window&amp;gt;&lt;/code&gt; is inside of the &lt;code&gt;&amp;lt;zk&amp;gt;&lt;/code&gt; tag, it is still the root component (since it has no parent component in the &lt;code&gt;.zul&lt;/code&gt; file). Also, although the .zul file has been included in the main file, it seems that it still has its own life cycle and its root component is unchanged.&lt;br/&gt;&lt;br/&gt;&lt;div class='zemanta-pixie'&gt;&lt;img src='http://img.zemanta.com/pixy.gif?x-id=9f8beae3-944e-88be-bb56-6a4598ef081a' class='zemanta-pixie-img'/&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1101600594018552935?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1101600594018552935/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1101600594018552935' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1101600594018552935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1101600594018552935'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/04/master-detail-view-in-zk.html' title='Master-Detail View in ZK'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-2446319049668436649</id><published>2009-04-09T22:34:00.004+10:00</published><updated>2009-04-09T22:50:10.196+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><title type='text'>Error: Not enough storage...</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;For the last few days I was having problems on my IE6. Every time I use Outlook WebMail to open a modal window - e.g. bringing up the address book, I would be greeted with an error message box saying '&lt;i&gt;not enough storage is available to complete this operation&lt;/i&gt;'. I tolerated the problem for all this time because I could still send mails and attach files, etc. But today, I had to use IE to create a travel request for my trip to Delhi and this problem is stopping me from filling in the request form (and the travel request web application does not work on Firefox). So I was stuck and the clock is ticking for I need to finish the travel request today.
&lt;p&gt;
After some digging from Google I found the &lt;a href='http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/d863c31c-8a50-4e9d-9482-ec479d330658/'&gt;solution&lt;/a&gt;: it was caused by the User Agent string being more than 260 characters long. Huh? I was gobsmacked when I saw this. But it really works: after I deleted all entries in the '&lt;i&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform&lt;/i&gt;', (don't forget to close all instances of IE after that) my problem disappeared. Then I did some experiment to figure out what was happening.
&lt;/p&gt;
&lt;p&gt;
First of all, it is well known among web designers/programmers that different browsers render the pages differently although HTML and CSS have been standardised for many years. Hence, the User Agent string is checked by many web applications to find out what browser is being used. In IE, the user agent can be retrieved using a simple javascript function: &lt;code&gt;navigator.userAgent&lt;/code&gt;. The way Microsoft IE gets the user agent string is by appending all the values under the above registry entry. You can see this by creating some new values under the above registry entry. You may get a user agent string like: &lt;i&gt;Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; New Value #1; lsfjsafj sf;dsfj sakf lksjf salkfjas lkfjlksfj safkjsa lkfsajf ;lksjflksajf lksa;fj salkfjsa kfdsalk)&lt;/i&gt;. As more values being inserted in the entry, the user agent string will get longer and longer, until it exceeds 260 characters, then it will simply return: &lt;i&gt;Mozilla/4.0 (compatible; MSIE 6.0)&lt;/i&gt;, obviously some default value without appending any values from the Windows registry. 
&lt;/p&gt;
&lt;p&gt;
That would have been fine if there were no other side effects. Unfortunately, maybe some other Microsoft jscript libraries have not catered for this and produce the 'not enough storage...' problem.
&lt;/p&gt;
&lt;P&gt;
In &lt;a href="http://jamazon.co.uk/web/2008/07/23/an-ie7-bug-that-returns-msie-60-user-agent-string/"&gt;his blog&lt;/a&gt;, James Thompson blamed toolbars and spywares for the extra values in the registry entry. In my case, all the values in the registry entry were from Microsoft - it looks like everytime I upgraded .NET, a new value was created:
&lt;/p&gt;
&lt;img src="http://dtt0ua.blu.livefilestore.com/y1psmK6TKUzX08vy6ZlFfwJH3D0u2wX9cqQgbENZQcSX18ccFUrMb4oQtAcP82bokSo4iZC0GAvvOARn8fK2aoqFLd4eDOmMleX/registry1.jpg" /&gt;
&lt;p&gt;So if you have been doggedly upgrading .NET all the way from 1.0 to 3.5 like me, then you would be experiencing the same problem as well. Looks like Microsoft does a more thorough job than those spyware vendors.
&lt;/p&gt;
&lt;br/&gt;&lt;br/&gt;&lt;div class='zemanta-pixie'&gt;&lt;img src='http://img.zemanta.com/pixy.gif?x-id=03bdbfd3-82d4-8991-ae84-fe57101ceddc' class='zemanta-pixie-img'/&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-2446319049668436649?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/2446319049668436649/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=2446319049668436649' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2446319049668436649'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2446319049668436649'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/04/error-not-enough-storage.html' title='Error: Not enough storage...'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-6586279867026812462</id><published>2009-02-17T14:07:00.004+11:00</published><updated>2009-02-22T09:30:45.060+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='N95'/><title type='text'>Fontrouter Open Sourced</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;I learned that &lt;a href='http://blog.oasisfeng.com/2009/02/15/source-code-of-fontrouter-is-released-under-apache-license/'&gt;Fontrouter has been open sourced&lt;/a&gt; under Apache License 2.0 two days ago. The download site has been moved to &lt;a href='http://code.google.com/p/fontrouter/'&gt;http://code.google.com/p/fontrouter/&lt;/a&gt;. This is both a good news and bad news.
&lt;p&gt;The good news is that there may be more people or organisations to work on the project, hopefully, giving it more resources that it deserves.&lt;/p&gt;
&lt;p&gt;The bad news is that oasisfeng may not provide as much support to the general public as he used to, which had been excellent in the past. The once very active &lt;a href="http://fontrouter.oasisfeng.com/forum/"&gt;Fontrouter Forum&lt;/a&gt; is now in ruins - real support activities have dwindled and spams are rampant.&lt;/p&gt;
&lt;p&gt;I sincerely hope that the Fontrouter project will continue and grow.&lt;/p&gt;
&lt;strong&gt;Related posts&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;a href='http://romenlaw.blogspot.com/2008/09/how-to-add-unicode-fonts-to-n95.html'&gt;How To Add Unicode Fonts to N95&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;br/&gt;&lt;br/&gt;&lt;div class='zemanta-pixie'&gt;&lt;img src='http://img.zemanta.com/pixy.gif?x-id=e63c8e6c-3ebb-48a3-a4c5-e671f65924c0' class='zemanta-pixie-img'/&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-6586279867026812462?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/6586279867026812462/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=6586279867026812462' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6586279867026812462'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6586279867026812462'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/02/fontrouter-open-sourced.html' title='Fontrouter Open Sourced'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5132710681510046371</id><published>2009-02-12T16:06:00.002+11:00</published><updated>2009-02-12T16:07:30.147+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>SyntaxHilighter 2</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;I have been using &lt;a href='http://alexgorbatchev.com/wiki/SyntaxHighlighter'&gt;SyntaxHighlighter &lt;/a&gt;1.5 on my blog site &lt;a href='http://romenlaw.blogspot.com/2008/06/highlightjs-vs-dpsyntaxhilighter.html'&gt;from day one&lt;/a&gt;. Since I don't have my own file server to host the SyntaxHighlighter files, I have been borrowing the URLs from &lt;a href='http://java.dzone.com'&gt;java.dzone&lt;/a&gt; &lt;img class='inlineimg' title='Wink' alt='' src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif' border='0'/&gt;.&lt;/p&gt;
&lt;p&gt;A few days ago, I noticed that all my code snippets in my blogs were not being processed. I first thought it was a temporary glitch from blogger.com or java.dzone. Today, I realised that SyntaxHighlighter has released v2.0 and obviously java.dzone had upgraded promptly. So I upgraded my blog site to use v2.0 as well by following the &lt;a href='http://alexgorbatchev.com/wiki/SyntaxHighlighter:Upgrading'&gt;official instructions&lt;/a&gt;. I also noticed that the author of SyntaxHighlighter is kind enough to &lt;a href='http://alexgorbatchev.com/wiki/SyntaxHighlighter:Hosting'&gt;host &lt;/a&gt;the files from the package which is good for bloggers like me (who do not have their own file servers on the internet).&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5132710681510046371?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5132710681510046371/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5132710681510046371' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5132710681510046371'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5132710681510046371'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/02/syntaxhilighter-2.html' title='SyntaxHilighter 2'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-3075636843359028809</id><published>2009-02-11T16:20:00.017+11:00</published><updated>2009-02-15T15:02:59.686+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Facebook'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Facebook API in Erlang</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;I have heard of Facebook APIs and Facebook applications so today I decided to check out what they can do.&lt;/p&gt;
&lt;p&gt;Based on documentation on Facebook, an Application is something that is hosted on an external server (such as your ISP or company web/application server) and can be invoked/accessed from a Facebook page. This seems very disappointing - I first thought a Facebook Application was something you can create using Facebook widgets and be hosted on Facebook servers. This model is not appealing to me, then again, I am not an advertiser.&lt;/p&gt;
&lt;p&gt;Nevertheless, I decided to give Facebook API a try. Looking through the list of supported languages, Java was discontinued by Facebook (what do you expect from a PHP shop?!) So the natural choice is Erlang &lt;img border='0' src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif' alt='' title='Wink' class='inlineimg'/&gt; using &lt;a href='http://github.com/ngerakines/erlang_facebook/tree/master'&gt;Erlang2Facebook&lt;/a&gt; client library. Before using the library, I had to prepare my environment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the latest &lt;a href='http://erlang.org/download.html'&gt;Erlang - OTP R12B (v5.6.5)&lt;/a&gt;. My old OTP R11B would not work because the library uses some new functions from the standard library (e.g. decode_packet).&lt;/li&gt;
&lt;li&gt;Download &lt;a href='http://code.google.com/p/mochiweb/source/browse/trunk/src/mochiweb_util.erl'&gt;mochiweb_util.erl&lt;/a&gt; and &lt;a href='http://code.google.com/p/mochiweb/source/browse/trunk/src/mochijson2.erl'&gt;mochijson2.erl&lt;/a&gt; as they are used by the erlang_facebook library.&lt;/li&gt;
&lt;li&gt;Download the &lt;a href='http://github.com/ngerakines/erlang_facebook/raw/07b74c2a62146e543f75e7bf3de406f6ed073310/src/erlang_facebook.erl'&gt;erlang_facebook.erl&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;a href='http://wiki.developers.facebook.com/index.php/API'&gt;Facebook APIs&lt;/a&gt; are RESTful services which support both &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; methods. Most API calls require a signature input parameter which is a MD5 hash of all the input parameters concatenated alphabetically. This is explained in &lt;a href='http://wiki.developers.facebook.com/index.php/Verifying_The_Signature'&gt;Facebook Developers Wiki&lt;/a&gt;. Also, many API calls require the &lt;code&gt;uid&lt;/code&gt; or &lt;code&gt;session_key&lt;/code&gt; as input parameters. It is a bit convoluted to get the session key:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create your own Application by following the &lt;a href='http://developers.facebook.com/get_started.php'&gt;instructions from Facebook&lt;/a&gt; so that you will get your own &lt;code&gt;API Key&lt;/code&gt;, &lt;code&gt;Application Secret&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;To get a &lt;code&gt;session_key&lt;/code&gt; value, you have to get &lt;code&gt;auth_token&lt;/code&gt; by accessing the URL: &lt;code&gt;http://www.facebook.com/login.php?api_key=1f5f...&lt;/code&gt;, which will forward to your application's host's URL with an input parameter for the &lt;code&gt;auth_key&lt;/code&gt;. In my case, it forwards to the URL: &lt;a href='http://romenlaw.blogspot.com/'&gt;http://romenlaw.blogspot.com&lt;/a&gt;/?auth_token=e1761... So now I have an &lt;code&gt;auth_token&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Once I have the &lt;code&gt;auth_token&lt;/code&gt;, I can call &lt;a href='http://wiki.developers.facebook.com/index.php/Auth.getSession'&gt;facebook.auth.getSession&lt;/a&gt; API to get the &lt;code&gt;session_key&lt;/code&gt; and &lt;code&gt;uid&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Erlang, this is shown below:&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
Erlang (BEAM) emulator version 5.6.5 [smp:2] [async-threads:0]

Eshell V5.6.5  (abort with ^G)
1&gt; c("/projects/facebook_erl/erlang_facebook.erl").
{ok,erlang_facebook}
2&gt; 
2&gt; c("/projects/facebook_erl/mochiweb_util.erl").
{ok,mochiweb_util}
3&gt; 
3&gt; c("/projects/facebook_erl/mochijson2.erl").
{ok,mochijson2}
4&gt; 
4&gt; [ApiKey, Secret, AppId]=["1fef...", "99f2...", "41..."].
...
9&gt; erlang_facebook:custom(ApiKey, Secret, "facebook.auth.getSession", [{"auth_token", "c92b9...this is the auth_token copied from step 2 above"}]).
{struct,[{&lt;&lt;"session_key"&gt;&gt;,
          &lt;&lt;"2.Ryk_v_nVtG..."&gt;&gt;},
         {&lt;&lt;"uid"&gt;&gt;,109...},
         {&lt;&lt;"expires"&gt;&gt;,123...}]}
...
19&gt; erlang_facebook:custom(ApiKey, Secret, "facebook.users.getLoggedInUser", [{"session_key", SessionKey}]).
109...(same as the uid returned by getSession call above)
28&gt; [Fid]=erlang_facebook:custom(ApiKey, Secret, "facebook.friends.get", [{"uid", "1092201851"}]).
[598...]
31&gt; erlang_facebook:custom(ApiKey, Secret, "facebook.friends.get", [{"uid", "598..."}]).            
{struct,[{&lt;&lt;"error_code"&gt;&gt;,10},
         {&lt;&lt;"error_msg"&gt;&gt;,
          &lt;&lt;"Application does not have permission for this action"&gt;&gt;},
         {&lt;&lt;"request_args"&gt;&gt;,
...
36&gt; erlang_facebook:custom(ApiKey, Secret, "facebook.users.getInfo", [{"uids", "109..."},{"fields", "uid, first_name, last_name, name, sex, birthday, affiliations, locale, profile_url, proxied_email"}]).
[{struct,[{&lt;&lt;"affiliations"&gt;&gt;,
           [{struct,[{&lt;&lt;"nid"&gt;&gt;,67...},
                     {&lt;&lt;"name"&gt;&gt;,&lt;&lt;"Australia"&gt;&gt;},
                     {&lt;&lt;"type"&gt;&gt;,&lt;&lt;"region"&gt;&gt;},
                     {&lt;&lt;"status"&gt;&gt;,&lt;&lt;&gt;&gt;},
                     {&lt;&lt;"year"&gt;&gt;,0}]}]},
          {&lt;&lt;"birthday"&gt;&gt;,null},
          {&lt;&lt;"first_name"&gt;&gt;,&lt;&lt;"Romen"&gt;&gt;},
          {&lt;&lt;"last_name"&gt;&gt;,&lt;&lt;"Law"&gt;&gt;},
          {&lt;&lt;"name"&gt;&gt;,&lt;&lt;"Romen Law"&gt;&gt;},
          {&lt;&lt;"sex"&gt;&gt;,null},
          {&lt;&lt;"uid"&gt;&gt;,109...},
          {&lt;&lt;"locale"&gt;&gt;,&lt;&lt;"en_US"&gt;&gt;},
          {&lt;&lt;"profile_url"&gt;&gt;,
           &lt;&lt;"http://www.facebook.com/people/Romen-Law/109..."&gt;&gt;},
          {&lt;&lt;"proxied_email"&gt;&gt;,
           &lt;&lt;"apps+419..."...&gt;&gt;}]}]
&lt;/pre&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-3075636843359028809?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/3075636843359028809/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=3075636843359028809' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3075636843359028809'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3075636843359028809'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/02/facebook-api-in-erlang.html' title='Facebook API in Erlang'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1648274846945000714</id><published>2009-01-23T14:53:00.005+11:00</published><updated>2009-01-25T21:01:12.366+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><title type='text'>Happy 牛 Year!</title><content type='html'>&lt;h1&gt;&lt;font color="#ff0000"&gt;Happy New Year of the Ox!&lt;/font&gt;&lt;/h1&gt;
&lt;script src="http://dl.javafx.com/dtfx.js"&gt;
&lt;!-- happy new year --&gt;
&lt;/script&gt;
&lt;script&gt;
    javafx(
        {
              draggable: false,
              width: 700,
              height: 380,
              code: "newyear.Main",
              name: "NewYear",
              jnlp_href: "http://sites.google.com/site/romenlaw/Home/NewYear_browser.jnlp"
        }
    );
&lt;/script&gt;
Source code available &lt;a href="http://sites.google.com/site/romenlaw/Home/NewYear_src.zip"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1648274846945000714?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1648274846945000714/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1648274846945000714' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1648274846945000714'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1648274846945000714'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/01/happy-year.html' title='Happy 牛 Year!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-6195757390565182687</id><published>2009-01-21T22:00:00.006+11:00</published><updated>2009-01-24T13:21:03.488+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><title type='text'>JavaFX MediaPlayer Memory Leak?</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;I wrote an &lt;a href="http://romenlaw.blogspot.com/2009/01/happy-year.html"&gt;e-card &lt;/a&gt;using JavaFX 1.0 to celebrate the upcoming Chinese New Year. It's a typical little multimedia applet with some animation, music and sound effect - supposedly perfect for JavaFX. However, I found that the memory usage is steadily climbing even when there is no activity (animation) happening on the canvas. I refactored, double-checked, triple-checked my source code several times to make sure that there was no unnecessary object creation and to reuse objects (by changing the opacity) every time - but to no avail.&lt;/p&gt;
&lt;p&gt;Then I did a little experiment and found out that the number-one culprit could be the &lt;code&gt;javafx.scene.media.MediaPlayer&lt;/code&gt;. The test program has a &lt;code&gt;MediaPlayer&lt;/code&gt; and blank canvas with two buttons - the &lt;code&gt;Start&lt;/code&gt; button to play the media/music; and the &lt;code&gt;Stop&lt;/code&gt; button to stop the music. The source code for this simple test is shown below.&lt;/p&gt;
&lt;pre class='java' name='code'&gt;
package testjavafx;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.ext.swing.SwingButton;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.scene.Scene;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;

var m=MediaPlayer {
    autoPlay: false
    repeatCount: MediaPlayer.REPEAT_FOREVER
    media: Media {
        source: "{__DIR__}bubugao.mp3"
    }
};
Stage {
    title: "Application title"
    width: 250
    height: 250
    scene: Scene {
        content: [
            MediaView {
                mediaPlayer: m
            }
            SwingButton {
                translateY:100
                text: "Start"
                action: function() {  
                    m.play();
                }
            },
            SwingButton {
                translateY: 150
                text: "Stop"
                action: function() {  
                    m.stop();
                }
            }
        ]
    }
}
&lt;/pre&gt;
&lt;p&gt;Profiling the application, I found the same memory usage pattern: memory usage climbs steadily with every time the media is played (i.e. pressing the &lt;code&gt;Start&lt;/code&gt; button). The heap graphs below are captured from NetBeans 6.5.&lt;/p&gt;
&lt;img src='http://sites.google.com/site/romenlaw/Home/profile.png'/&gt;
&lt;p&gt;The notes on the first graph (on the left) are explained below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;play&lt;/code&gt; - the &lt;code&gt;Start&lt;/code&gt; button was pressed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GC&lt;/code&gt; - garbage collection was forced by pressing the GC icon several times in NetBeans&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stop&lt;/code&gt; - the &lt;code&gt;Stop&lt;/code&gt; button was pressed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GC&lt;/code&gt; - garbage collection was forced&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The notes on the second graph (on the right) are explained below:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;GC&lt;/code&gt; - garbage collection was forced by pressing the GC icon several times in NetBeans&lt;/li&gt;
&lt;li&gt;&lt;code&gt;play&lt;/code&gt; - the &lt;code&gt;Start&lt;/code&gt; button was pressed &lt;strong&gt;several times quickly&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stop&lt;/code&gt; - the &lt;code&gt;Stop&lt;/code&gt; button was pressed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GC&lt;/code&gt; - garbage collection was forced&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The MediaPlayer also has very limited support on media formats - it does not support wave files, or MPEG-2.5 sound files... so that I couldn't use most of the sound-effect files available on the internet. So this is Sun's solution to multimedia applications?!&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-6195757390565182687?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/6195757390565182687/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=6195757390565182687' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6195757390565182687'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6195757390565182687'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/01/javafx-mediaplayer-memory-leak.html' title='JavaFX MediaPlayer Memory Leak?'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1088100281689184363</id><published>2009-01-07T11:13:00.001+11:00</published><updated>2009-01-08T10:00:13.483+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><title type='text'>Moon Monsters in JavaFX</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;The &lt;a href='http://www.adamkinney.com/studios/moonmonsters/'&gt;Moon Monsters &lt;/a&gt;demo shows up as the first sample in Microsoft's &lt;a href='http://silverlight.net/community/gallerydetail.aspx?cat=3&amp;amp;sort=2'&gt;Silverlight 1.0 Gallery&lt;/a&gt;. I thought it'd be great to test-drive JavaFX by porting this demo from Silverlight to JavaFX 1.0. &lt;/p&gt;
&lt;img src='http://silverlight.net/themes/silverlight/images/community/user/MoonMonsters1.png'/&gt;
&lt;p&gt;This seemingly simple demo actually touches on quite a few areas in the core stength of the JavaFX APIs - 2D graphics, data binding and input events handling. While the JavaFX port of the Moon Monsters is a pretty faithful implementation of the original features, it is not 100% complete. The following are not implemented here:&lt;/p&gt;
&lt;p&gt;
&lt;ol&gt;
&lt;li&gt;The graphics for paintbrush and keyboard are not included because it is too laborious to copy the coordinates into the JavaFX script.&lt;/li&gt;
&lt;li&gt;The HTML Overlay feature is not implemented. I don't know how to do this in JavaFX because unlike Silverlight 1.0, JavaFX is not Javascript based and it is not bound to HTML either. If anyone knows how to do this in JavaFX, please leave a comment.&lt;/li&gt;
&lt;/ol&gt;
The source files are available here: &lt;a href='http://sites.google.com/site/romenlaw/Home/alien.zip?attredirects=0'&gt;alien.zip&lt;/a&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;script src='http://dl.javafx.com/dtfx.js'&gt;

&lt;/script&gt;
&lt;script&gt;
    javafx(
        {
              draggable: false,
              width: 900,
              height: 600,
              code: "alien.Main",
              name: "Alien",
              jnlp_href: "http://sites.google.com/site/romenlaw/Home/Alien_browser.jnlp"
        }
    );
&lt;/script&gt;
&lt;/div&gt;
&lt;b&gt;Related Posts:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href='http://romenlaw.blogspot.com/2008/12/happy-new-year.html'&gt;Happy New Year&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1088100281689184363?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1088100281689184363/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1088100281689184363' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1088100281689184363'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1088100281689184363'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/01/moon-monsters-in-javafx_07.html' title='Moon Monsters in JavaFX'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7115103042520999060</id><published>2009-01-02T21:40:00.005+11:00</published><updated>2009-01-06T11:43:28.156+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GWT'/><title type='text'>Problem Mixing SmartSWT and GWT Widgets</title><content type='html'>&lt;p&gt;I set out implementing my &lt;a href="http://romenlaw.blogspot.com/2008/12/gwt-vs-zk-sloc-count.html"&gt;Address GUI&lt;/a&gt; using &lt;a href="http://code.google.com/p/smartgwt/"&gt;SmartGWT&lt;/a&gt; 1.0b1 together with &lt;a href="http://code.google.com/webtoolkit/overview.html"&gt;GWT&lt;/a&gt; 1.5.3 for Windows - both are latest releases available for download. The SmartGWT does not have any formal documentation. Information are scattered in &lt;a href="http://www.smartclient.com/smartgwt/javadoc/"&gt;Javadoc&lt;/a&gt;, &lt;a href="http://www.smartclient.com/smartgwt/showcase/"&gt;showcase &lt;/a&gt;and developer/user &lt;a href="http://forums.smartclient.com/"&gt;forums&lt;/a&gt;. Although not ideal, the documentation is generally adequate.&lt;/p&gt;
&lt;p&gt;I quickly ran into problem with SmartGWT. I wanted to have a menu bar at the top of my application as shown in the following diagram (which is a screenshot of SWT-Ext implementation of the same application).&lt;/p&gt;
&lt;img src='http://4qarqw.blu.livefilestore.com/y1p3AC7q1-oZX2FfjrXHtmccYxhUfPFN85wM025uTaDbCk9xwPn5KnR6RtxmKVxZrfz9_IokfbhnHUyFu2ylbqLXg/gwt_crud.png'/&gt;
&lt;p&gt;SmartGWT does have a &lt;a href="http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/widgets/menu/MenuBar.html"&gt;MenuBar &lt;/a&gt;in its API. However, this class does not seem fully implemented - there is no method to add menus (the addMenus() method is missing from the download although it appears in the online javadoc). As a workaround, I decided to use GWT's own &lt;a href="http://google-web-toolkit.googlecode.com/svn/javadoc/1.5/com/google/gwt/user/client/ui/MenuBar.html"&gt;MenuBar &lt;/a&gt;and &lt;a href="http://google-web-toolkit.googlecode.com/svn/javadoc/1.5/com/google/gwt/user/client/ui/MenuItem.html"&gt;MenuItem &lt;/a&gt;in combination with SmartGWT's &lt;a href="http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/widgets/toolbar/ToolStrip.html"&gt;ToolStrip &lt;/a&gt;and &lt;a href="http://www.smartclient.com/smartgwt/javadoc/com/smartgwt/client/widgets/tab/TabSet.html"&gt;TabSet &lt;/a&gt;widgets as shown below.&lt;/p&gt;
&lt;img src="http://dtt0ua.blu.livefilestore.com/y1p0dVgw8ZpBuRRZGBu6rdlOYoanT7XATqvcW4ugYDK8v2a9R0FACTaFkbPGMcCE1V3mhPUqt5jODlqLyBXQbActQ/SmartGWT.png"/&gt;
&lt;p&gt;I have done the same thing with the SWT-Ext version of the Address GUI (as shown in the first diagram above) using GWT's Menu with SWT-Ext's ToolBar and TabPanel widgets without any problems. However, in SmartGWT, the drop-down menu appears behind the SmartGWT widgets and get obscured by them. So I cannot really use the menu items any more. This result is shown in the above diagram.&lt;/p&gt;
&lt;p&gt;It seams that SmartGWT is not so smart after all. &lt;span style="font-weight:bold;"&gt;[Update 2009-01-03]:&lt;/span&gt; Just joking!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7115103042520999060?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7115103042520999060/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7115103042520999060' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7115103042520999060'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7115103042520999060'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2009/01/problem-mixing-smartswt-and-gwt-widgets.html' title='Problem Mixing SmartSWT and GWT Widgets'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4284988891217789558</id><published>2008-12-31T23:18:00.004+11:00</published><updated>2009-01-08T10:02:28.111+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><title type='text'>Happy New Year</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;script src='http://dl.javafx.com/dtfx.js'&gt;

&lt;/script&gt;
&lt;script&gt;
    javafx(
        {
              draggable: true,
              width: 390,
              height: 540,
              code: "merrychristmas.Main",
              name: "MerryChristmasJavaFX",
              jnlp_href: "https://sites.google.com/site/romenlaw/Home/MerryChristmasJavaFX_browser.jnlp"
        }
    );
&lt;/script&gt;
&lt;p&gt;This e-card was written in JavaFX. The source code can be found &lt;a href='http://romenlaw.blogspot.com/2008/12/merry-christmas-javafx-10.html'&gt;here&lt;/a&gt;.
&lt;/p&gt;
If you encounter problems seeing it, &lt;a href='http://romenlaw.blogspot.com/2008/12/sorry-state-of-javafx-10.html'&gt;I am not surprised&lt;/a&gt;.
&lt;p&gt;
&lt;b&gt;Related Posts:&lt;/b&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href='http://romenlaw.blogspot.com/2009/01/moon-monsters-in-javafx_07.html'&gt;Moon Monsters In JavaFX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4284988891217789558?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4284988891217789558/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4284988891217789558' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4284988891217789558'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4284988891217789558'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/12/happy-new-year.html' title='Happy New Year'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8981667390103975500</id><published>2008-12-25T14:29:00.004+11:00</published><updated>2008-12-31T17:43:32.145+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Management'/><title type='text'>Merry Christmas!</title><content type='html'>&lt;p&gt;2008 has been a disastrous year in many aspects all over the world - from cyclone Nargis to the earth quake in SiChuan China; from Mumbai Massacre to  mobocracy in Thailand...  and let's not forget the  global economic crisis...&lt;/p&gt;
&lt;p&gt;Now more to home, following the down-sizing trend a company that I know of has decided to wind down the operation in Sydney just 2 weeks before christmas. As a result, people in the Sydney office have been made redundant. Come to think of it, the holiday season is perfect to fire people. Why? During Christmas, New Year and Australia Day (January 26) people's productivity is not at its peak. Firing them during this period will save money for the company left and right - it is a win-win situation for the company! Being a profit-chasing business entity, it is perfectly logical to 'do the right thing' in the holiday season. After all, humans are nothing but resources to a company - hence the term &lt;em&gt;HR&lt;/em&gt;.&lt;/p&gt; 
&lt;p&gt;In the story, Mr Scrooge came to his senses and became a better human being in the end. But in real life, the reverse is usually true...&lt;/p&gt;
&lt;img src="http://js3.pp.sohu.com.cn/ppp/images/emotion/b/beat.gif"/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8981667390103975500?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8981667390103975500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8981667390103975500' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8981667390103975500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8981667390103975500'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/12/merry-christmas.html' title='Merry Christmas!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8132310016729976201</id><published>2008-12-20T20:52:00.006+11:00</published><updated>2008-12-22T11:23:29.144+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='N95'/><title type='text'>Turn Off 3G To Save The Environment!</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;My N95 battery could never last more than 16 hours even when sitting idle. I had to recharge it every night. This week when I was in Kathmandu, I was pleasantly surprised: my battery actually lasted over 36 hours during which I snapped more than 70 &lt;a href="http://picasaweb.google.com/romen.law/Nepal#"&gt;photos &lt;/a&gt;, played 20 minutes of music and had a couple of phone calls (totaling over 15 minutes of talk time maybe). I noticed that there was no 3G network in Nepal (or at least I was not roamed to one). I believe that is why my battery lasted so long. That fact that I live on the edge of the cell probably exacerbated the situation.&lt;/p&gt;
&lt;p&gt;So I decided to disable 3G on my phone. Normally, this is a straight forward operation available from the Settings -&amp;gt; Phone menu. However, my phone was from 3 Australia (product code: 0547465) and it was branded - meaning that it was customised for 3 Australia: some 3 applications was installed, some menu items have been disabled... and the menu item to turn off 3G has been disabled. Super keen on saving the environment by extending my battery life &lt;img border='0' src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif' alt='' title='Wink' class='inlineimg'/&gt; I set out to debrand the phone and turn off 3G!&lt;/p&gt;
&lt;p&gt;The process of debranding is as following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Backup the phone using PC Suite&lt;/li&gt;
&lt;li&gt;Change the product code by following the &lt;a href='http://www.allaboutsymbian.com/forum/showthread.php?t=67130'&gt;instructions&lt;/a&gt;. The new product code I used is &lt;b&gt;0536087&lt;/b&gt;. Note that the NSS could not detect any phone on my notebook. In the end I installed NSS on my desktop (also running XP) using root account and that all worked fine. I don't really know why it would not work on my laptop.&lt;/li&gt;
&lt;li&gt;Update the firmware using Nokia Software Updater&lt;/li&gt;
&lt;li&gt;Restore the phone using PC Suite. The restore is not perfect and I still have to re-arrange/reinstall some applications. And all my messages and contacts are lost!&lt;img src='http://www.gearthhacks.com/forums/images/smilies/crysmilie.gif'/&gt;&lt;/li&gt;
&lt;/ol&gt;
But it was all worth it. Now under the Tools -&amp;gt; Settings -&amp;gt; Phone -&amp;gt; Network menu I have an extra item: &lt;i&gt;Network mode&lt;/i&gt; where I can select &lt;i&gt;GSM &lt;/i&gt;only!
&lt;p&gt;On the negative side, there are some major drawbacks upgrading to the 30.x version of the firmware. TRK no longer works and I had to install alternative (but better) solution. &lt;a href="http://romenlaw.blogspot.com/2008/09/how-to-add-unicode-fonts-to-n95.html"&gt;Fontrouter &lt;/a&gt;does not seem to work any more in the new browser (because it does not have the character encoding that I need) and I got around the problem by installing &lt;a href="http://www.opera.com/mini/"&gt;Opera Mini&lt;/a&gt; instead.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8132310016729976201?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8132310016729976201/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8132310016729976201' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8132310016729976201'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8132310016729976201'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/12/turn-off-3g-to-save-environment.html' title='Turn Off 3G To Save The Environment!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1732770132850059765</id><published>2008-12-16T12:46:00.014+11:00</published><updated>2010-01-29T21:52:37.957+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><title type='text'>The Sorry State of JavaFX 1.0</title><content type='html'>The release of JavaFX 1.0 officially marks Sun's defeat in the RIA battle. Why? Because JavaFX 1.0 is inferior to its competitors in all major battlefronts of the RIA war. It looks like a rushed job to make a marketing deadline.
&lt;h2&gt;The JavaFX 1.0 APIs&lt;/h2&gt;
&lt;p&gt;The JavaFX 1.0 is shipped with 2D graphical capabilities and that's pretty much it. Even basic things such as &lt;a href="http://www.sun.com/software/javafx/index.jsp"&gt;3D graphics&lt;/a&gt;, &lt;a href="http://javafx.com/launch/mac-player.jsp"&gt;faster video codecs&lt;/a&gt; are labeled with 'coming soon...' on their demo site. Forget about building any business RIAs because there are only a handful of simple Swing widgets at your disposal. On the other hand, people have been using Flash to build business applications for years; Silverlight has got 3D capabilities since v1.0.&lt;/p&gt;
&lt;h2&gt;Design/Development&lt;/h2&gt;
&lt;p&gt;The JavaFX plugin for NetBeans is a joke - it's buggy, sluggish and poor in features. One mistyped word or bracket can result in red squiggly lines all over the screen; then it produces unhelpful error hints such as 'sorry I was expecting so and so but I saw such as such...' so that the user has to read through the whole sentence several times and still hard to figure out what the complaint is. What happens to KISS principle?&lt;/p&gt;
&lt;p&gt;Sure, Sun had said that they intended the plugin to be used by designers also - hence the 'user friendly' error messages. But let's get serious, how can anyone assume graphical designers will be using computer programming language to define simple things such as Timelines? Sun doesn't have to look far to find a better way - Microsoft has Expression Web to be used by graphical designers, and VS.NET 2008 to be used by programmers when it comes to Silverlight design and development workflow. (The JavaFX plugin for Illustrator is only good for editing shapes).&lt;/p&gt;
&lt;h2&gt;Deployment&lt;/h2&gt;
&lt;p&gt;To me there is not much point of deploying RIA as a thick client using Java Web Start. I am only interested in running RIA in a browser. With the new way of deploying applets in Java, JNLP is used, so that the HTML file points to a JNLP file and the JNLP file points to the JAR files and more JNLP files... It's a bit like my multi-hop flight from Sydney to Kathmandu (via Bristane then Bangkok) that took me over 20 hours and 6 of which was waiting at BKK for transit (that that is how I have the time to write this post) all because my company was too cheap to book the Qantas flight.&lt;/p&gt;
&lt;p&gt;This is unnecessarily complicated because Sun doesn't want to have JavaFX plugin to the browser. On the other hand, Flash/Silverlight plugins are installed onto the browser so that all you need to specify is the &lt;code&gt;object&lt;/code&gt; or &lt;code&gt;embed&lt;/code&gt; tag in the HTML file without the hassle of running the myriad javascripts to detect/download the additional framework JAR files every time. &lt;/p&gt;
&lt;h2&gt;The User Experience&lt;/h2&gt;
&lt;p&gt;If you are using a browser that does not have the latest Java plugin (that supports JavaFX) installed (such as on my Nokia N95, or using the PC in the Thai Airways lounge at Kathmandu international airport), then you will be greeted with a message box forcing you to redirect to Sun's download website. There is no option to cancel!&lt;/p&gt;
Moreover, those javascripts do not report any errors if something goes wrong - it just hangs there with the Java logo spinning around and around forever...&lt;/p&gt;
&lt;img src="http://dtt0ua.blu.livefilestore.com/y1pgcn-iwHc75UCxsKUWZqxu9BjHkcOMjPlRp3tDlL6gyD55VTDL84E3EX5j2tvJpWVqv-HFjzAlRmdca5HJ51dykrFxjB_zMBn/javafx-loading-100x100.gif"/&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1732770132850059765?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1732770132850059765/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1732770132850059765' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1732770132850059765'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1732770132850059765'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/12/sorry-state-of-javafx-10.html' title='The Sorry State of JavaFX 1.0'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-3231917116841237941</id><published>2008-12-10T22:40:00.025+11:00</published><updated>2009-01-30T19:14:38.886+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><title type='text'>Merry Christmas JavaFX 1.0</title><content type='html'>&lt;p&gt;&lt;a href="http://www.sun.com/software/javafx/index.jsp"&gt;JavaFX 1.0 was finally released&lt;/a&gt; last Friday. After over 2 years of brooding, I wonder what Sun has come up with - oh boy what a disappointment the JavaFX IDE (NB plug-in) is! But that is for another post.
&lt;/p&gt;
&lt;p&gt;The JavaFX 1.0 is no good for developing any RIA business applications for its lack of widgets. Instead it is OK for little toys such as those demos published on &lt;a href="http://www.javafx.com"&gt;javafx.com&lt;/a&gt;. Immersed in the holiday season atmosphere, I implemented my Christmas card in JavaFX. A screenshot is shown below.
&lt;/p&gt;
&lt;img src="http://dtt0ua.blu.livefilestore.com/y1pagckjvMcZAzbBgJ-
IInrdw443fpoYLyXhwwSGkeWBOBGE0Na0v0JG0fxBMTfGMWzm9NlsEkA7JM/xmas_card.png"/&gt;
&lt;p&gt;The Christmas card has the following features:
&lt;ul&gt;
&lt;li&gt;Transformation - the red and yellow text fly into the screen using a combination of Translation, Rotation and Scale.&lt;/li&gt;
&lt;li&gt;Media playback - background music (mp3 file) from &lt;a href="http://www.michaelbuble.com/"&gt;Michael Buble - Let it snow&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Animation - snow fall simulation&lt;/li&gt;
&lt;li&gt;Drag out - when run as applet in a browser, the card can be dragged out of the browser.
&lt;/ul&gt;
&lt;/p&gt;
&lt;h2&gt;The Project Structure&lt;/h2&gt;
&lt;p&gt;The project was created using &lt;a href="http://www.netbeans.org/community/releases/65/"&gt;NetBeans 6.5&lt;/a&gt; with JavaFX plug-ins (installed from within NetBeans). I created a JavaFX project called MerryChristmas. The project structure is shown below:&lt;/p&gt;
&lt;img src="http://dtt0ua.blu.livefilestore.com/y1pAsrJqUaaSeIDUVKhZPrUOAl4QwsMnwjp9mOSzvqlq6kCwm4oGbwcsTwy91ZwC4gVLobULoGlIYXAewYrfRGwEQ/xmas_proj.png"/&gt;
&lt;p&gt;The only files that I added are:
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;LetItSnow.mp3&lt;/code&gt; - the background music song&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tree.png&lt;/code&gt; - the background image: photo of Christmas tree shot at &lt;a href="http://www.darlingharbour.com/"&gt;Darling Harbour&lt;/a&gt; on December 8, 2008.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Main.fx&lt;/code&gt; - the main JavaFX file containing the scene and its contents&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SnowFall.fx&lt;/code&gt; - the &lt;code&gt;CustomNode&lt;/code&gt; simulating snow fall and snow flakes (using circle)&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;
&lt;h2&gt;The Main Contents&lt;/h2&gt;
&lt;p&gt;The contents of the main scene are two Texts, one background image, one background music and the snow fall (implemented as &lt;code&gt;CustomNode&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The texts and their animation (actually transformation) are created as shown below. Notice the transforms which use binding data changed by the Timeline definition.&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
...
var x:Number;
var y: Number;
var scaleX : Number;
var scaleY:Number;
var angle:Number;

Timeline {
    repeatCount: 1
    keyFrames: [
        at (0s) {x=&gt;180; y =&gt; 250; scaleX =&gt; 0.0; scaleY =&gt; 0.0; angle =&gt; -180},
        at (10s) {x=&gt; 0; y =&gt; 0;
        scaleX =&gt; 1.0; scaleY =&gt; 1.0;
        angle =&gt; 0
        tween Interpolator.EASEOUT},
    ]
}.play();
...
var lighting = Lighting{
    light: DistantLight{
        azimuth: 60,
        elevation: 70
    }
    surfaceScale: 5
};

Stage {
    ...
    scene: Scene {
        content: [
            ...
            Text {
                content: "Merry Christmas &amp; \nHappy New Year!"
                font: Font.font("Arial Bold", FontWeight.BOLD, 40)
                textOrigin: TextOrigin.TOP
                textAlignment: TextAlignment.CENTER
                x: 10
                y: 20
                transforms: bind[
                    Translate{
                        x:x
                        y:y}
                    Scale{
                        x:scaleX
                        y:scaleY}
                    Rotate{
                        angle: -angle
                        pivotX: 180
                        pivotY: 20 }
                ]
                fill: Color.RED
                effect: lighting
            },
            Text {
                content: "From Romen"
                font: Font.font("Times", FontWeight.BOLD, 40)
                textOrigin: TextOrigin.TOP
                x: 90
                y: 470
                transforms: bind[
                    Translate{
                        x:x
                        y:y}
                    Scale{
                        x:scaleX
                        y:scaleY}
                    Rotate{
                        angle: angle
                        pivotX: 180
                        pivotY: 450}
                ]
                fill: Color.YELLOW
                effect: lighting
            },
...
&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;Any suggestions on how to make unicode text work are welcome.&lt;/b&gt;&lt;/p&gt;
The background image and music are easy enough:
&lt;pre name="code" class="java"&gt;
...
            ImageView {
                image: Image {
                    url: "{__DIR__}tree.png"
                }
                x:0,
                y:0
            },
...
            MediaView {
                mediaPlayer: MediaPlayer {
                    autoPlay: true
                    media: Media {
                        source: "{__DIR__}LetItSnow.mp3"
                    }
                }
            },
...
&lt;/pre&gt;
&lt;h2&gt;Drag Out&lt;/h2&gt;
To enable drag out when run as an applet in browser, I added the following extension to the &lt;code&gt;Stage&lt;/code&gt;:
&lt;pre name="code" class="java"&gt;
    extensions: [
        AppletStageExtension {
            shouldDragStart: function(e): Boolean {
                return e.primaryButtonDown;
            }
            useDefaultClose: false
        }
    ]
&lt;/pre&gt;
Also I had to specify in the Project's properties dialog and check the Draggable checkbox so that it will generate the &lt;code&gt;.html&lt;/code&gt; file with the &lt;code&gt;draggable&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;. The generated &lt;code&gt;.html&lt;/code&gt; file snippet:
&lt;pre name="code" class="html"&gt;
...
&lt;script&gt;
    javafx(
        {
              archive: "MerryChristmasJavaFX.jar",
              draggable: true,
              width: 390,
              height: 540,
              code: "merrychristmas.Main",
              name: "MerryChristmasJavaFX"
        }
    );
&lt;/script&gt;
...
&lt;/pre&gt;
&lt;h2&gt;Snow Fall&lt;/h2&gt;
All snow related classes are in the &lt;code&gt;SnowFall.fx&lt;/code&gt; file. I used white circle as the snow flake:
&lt;pre name="code" class="java"&gt;
public class SnowFlake extends Circle {
    init {
        fill = Color.WHITE;
        radius = 3 + Math.random() * 3;
        opacity= Math.random() * 0.5 + 0.5;
    }
}
&lt;/pre&gt;
The &lt;code&gt;SnowFall&lt;/code&gt; class takes 3 attributes:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;height, width&lt;/code&gt; - these specify the size of the canvas so that all the snow flakes can spread out in the canvas making it more realistic&lt;/li&gt;
&lt;li&gt;&lt;code&gt;numberOfFlakes&lt;/code&gt; - the number of snow flakes to be drawn and animated. The more flakes, the more threads JavaFX will have to create and manage under the hood.&lt;/li&gt;
&lt;/ul&gt;
The animation of the snow flakes are a bit tricky. Initially all the snow flakes are generated and randomly placed around the canvas; then, they start to fall. The movement of the snowflakes are as such: every second the snow flake will move left (negative) or right (positive) by a random amount, and move down by a random amount - i.e. the horizontal movement (x) can be either positive or negative, the vertical movement (y) is positive only. This is shown below:
&lt;pre name="code" class="java"&gt;
                            KeyFrame {
                                ...
                                values: [
                                    x =&gt;
                                    x + (Math.random() - 0.5)
                                    y =&gt;
                                    y + Math.random() tween Interpolator.LINEAR
                                ]
...
&lt;/pre&gt;
When the snow flake hits either side of the boundary, it sort of bounces back a little; when it moves off the bottom of the canvas, it is wrapped around and re-appears at the top of the canvas and falls down again. This is achieved by using &lt;code&gt;action&lt;/code&gt; of &lt;code&gt;Keyframe&lt;/code&gt; which are executed at the beginning of each key frame.
&lt;pre name="code" class="java"&gt;
...
                               action: function() {
                                    if(flake.centerY+y &gt;height) {
                                        y=0; flake.centerY=0;
                                    }
                                    if(flake.centerX+x&gt;width) {
                                        x=0; flake.centerX=width;
                                    }
                                    if(flake.centerX+x&lt;0) {
                                        x=0; flake.centerX=0;
                                    }
                                }
...
&lt;/pre&gt;
The &lt;code&gt;SnowFall&lt;/code&gt; class generates all the snow flakes in a &lt;code&gt;for&lt;/code&gt; loop. Each snow flake is accompanied by a &lt;code&gt;Timeline&lt;/code&gt; controlling its animation. The number of frames in each &lt;code&gt;Timeline&lt;/code&gt; is also randomised (between 60 and 120 seconds per cycle), to avoid the situation where all snow flakes suddenly reset at the same time.
&lt;pre name="code" class="java"&gt;
for ( i in [1..numberOfFlakes]) {
...
                    def timer = 60+60*Math.random();
                    Timeline {
                        repeatCount: Timeline.INDEFINITE
                        keyFrames: [ for (j in [0..timer]) {
                            KeyFrame {
...
&lt;/pre&gt;
Want to see the Christmas card in action? Well, you will have to wait till Christmas!&lt;img class="inlineimg" title="Wink" alt="" src="http://www.sunniforum.com/forum/images/smilies/icon_wink.gif" border="0" /&gt; &lt;strong&gt;[Update NYe2008]&lt;/strong&gt;Now that it is NYE 2008, click &lt;a href="http://romenlaw.blogspot.com/2008/12/happy-new-year.html"&gt;here&lt;/a&gt; to run the e-card.&lt;/p&gt; 
&lt;p&gt;Meanwhile, here are the complete source code.&lt;/p&gt;
&lt;h2&gt;SnowFall.fx&lt;/h2&gt;
&lt;pre name="code" class="java"&gt;
/*
 * SnowFall.fx
 *
 * Created on 10/12/2008, 09:55:19
 */

package merrychristmas;

import java.lang.Math;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.animation.KeyFrame;
import java.lang.System;

/**
 * @author ROMENL
 */

public class SnowFall extends CustomNode {
    public var height:Number;
    public var width:Number;
    public var numberOfFlakes:Number;

    override function create():Node {
        return Group {
            content: [
                for ( i in [1..numberOfFlakes]) {
                    var x:Number;
                    var y:Number;
                    x=1;
                    y=1;
                    var flake = SnowFlake{
                        centerX: Math.random() * width
                        centerY: Math.random() * height
                        translateX: bind x;
                        translateY: bind y;
                    }

                    def timer = 60+60*Math.random();
                    Timeline {
                        repeatCount: Timeline.INDEFINITE
                        keyFrames: [ for (j in [0..timer]) {
                            KeyFrame {
                                time: bind Duration.valueOf(j * 1000)
                                values: [
                                    x =&gt;
                                    x + (Math.random() - 0.5)
                                    y =&gt;
                                    y + Math.random() tween Interpolator.LINEAR
                                ]
                                action: function() {
                                    if(flake.centerY+y &gt;height) {
                                        y=0; flake.centerY=0;
                                    }
                                    if(flake.centerX+x&gt;width) {
                                        x=0; flake.centerX=width;
                                    }
                                    if(flake.centerX+x&lt;0) {
                                        x=0; flake.centerX=0;
                                    }
                                }
                            }
                        }
                        ]
                    }.play();
                    flake
                }]
            
        };
    }
}
public class SnowFlake extends Circle {
    init {
        fill = Color.WHITE;
        radius = 3 + Math.random() * 3;
        opacity= Math.random() * 0.5 + 0.5;
    }
}
&lt;/pre&gt;
&lt;h2&gt;Main.fx&lt;/h2&gt;
&lt;pre name="code" class="java"&gt;
/*
 * Main.fx
 *
 * Created on 9/12/2008, 15:29:24
 */

package merrychristmas;

import javafx.animation.*;
import javafx.scene.effect.*;
import javafx.scene.effect.light.*;
import javafx.scene.image.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.scene.paint.Color;
import javafx.scene.Scene;
import javafx.scene.text.*;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import javafx.stage.AppletStageExtension;
import javafx.stage.Stage;

/**
 * @author ROMENL
 */
var width=390;
var height=540;
var x:Number;
var y: Number;
var scaleX : Number;
var scaleY:Number;
var angle:Number;

Timeline {
    repeatCount: 1//Timeline.INDEFINITE
    keyFrames: [
        at (0s) {x=&gt;180; y =&gt; 250; scaleX =&gt; 0.0; scaleY =&gt; 0.0; angle =&gt; -180},
        at (10s) {x=&gt; 0; y =&gt; 0;
        scaleX =&gt; 1.0; scaleY =&gt; 1.0;
        angle =&gt; 0
        tween Interpolator.EASEOUT},
    ]
}.play();

var lighting = Lighting{
    light: DistantLight{
        azimuth: 60,
        elevation: 70
    }
    surfaceScale: 5
};

Stage {
    title: "Merry Xmas JavaFX"
    width: width
    height: height
    scene: Scene {
        content: [
            ImageView {
                image: Image {
                    url: "{__DIR__}tree.png"
                }
                x:0,
                y:0
            },
            Text {
                content: "Merry Christmas &amp; \nHappy New Year!"
                font: Font.font("Arial Bold", FontWeight.BOLD, 40)
                textOrigin: TextOrigin.TOP
                textAlignment: TextAlignment.CENTER
                x: 10
                y: 20
                transforms: bind[
                    Translate{
                        x:x
                        y:y}
                    Scale{
                        x:scaleX
                        y:scaleY}
                    Rotate{
                        angle: -angle
                        pivotX: 180
                        pivotY: 20 }
                ]
                fill: Color.RED
                effect: lighting
            },
            Text {
                content: "From Romen"
                font: Font.font("Times", FontWeight.BOLD, 40)
                textOrigin: TextOrigin.TOP
                x: 90
                y: 470
                transforms: bind[
                    Translate{
                        x:x
                        y:y}
                    Scale{
                        x:scaleX
                        y:scaleY}
                    Rotate{
                        angle: angle
                        pivotX: 180
                        pivotY: 450}
                ]
                fill: Color.YELLOW
                effect: lighting
            },
            MediaView {
                mediaPlayer: MediaPlayer {
                    autoPlay: true
                    media: Media {
                        source: "{__DIR__}LetItSnow.mp3"
                    }
                }
            },
            SnowFall {
                height: height
                width: width
                numberOfFlakes: 30
            }
        ]
    }
    extensions: [
        AppletStageExtension {
            shouldDragStart: function(e): Boolean {
                return e.primaryButtonDown;
            }
            useDefaultClose: false
        }
    ]
}
&lt;/pre&gt;
&lt;strong&gt;Related Posts:&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/12/happy-new-year.html"&gt;Happy New Year&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href='http://romenlaw.blogspot.com/2009/01/happy-year.html'&gt;Happy New Year of the Ox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/12/sorry-state-of-javafx-10.html"&gt;The Sorry State of JavaFX 1.0&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-3231917116841237941?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/3231917116841237941/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=3231917116841237941' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3231917116841237941'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/3231917116841237941'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/12/merry-christmas-javafx-10.html' title='Merry Christmas JavaFX 1.0'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8251396591165443226</id><published>2008-12-04T11:04:00.007+11:00</published><updated>2008-12-04T14:10:57.497+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><title type='text'>The Long Tail Theory</title><content type='html'>&lt;p&gt;In the Telco business we often talk about the long tail products/services illustrated in the following diagram.&lt;/p&gt;
&lt;img src="http://ripples.typepad.com/ripples/images/conceptual.jpg"/&gt;
&lt;p&gt;The idea is that for a service provider to stay competitive, it has to deploy and experiment with a large number of products and see how they are accepted by the market. There will be a small number of popular ones and the rest will be less popular as indicated in the long tail. So, over time, the service provider will kill the proven unpopular products and replace them with new/other products. You may wonder, why do they bother with the long tail since they are not that popular anyway? There are two major reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The service providers are too paranoid not to provide those services because they want to stay competitive - anything that their competitors have, they also want it.&lt;/li&gt;
&lt;li&gt;Other long tail graphs replace the &lt;i&gt;Popularity &lt;/i&gt;label with &lt;i&gt;Revenue&lt;/i&gt;. It's not hard to see that the total area under the Long Tail curve (i.e. total revenue) could add up to an handsome amount.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;How is this theory relevant to me? Well, being an IT professional, I am also paranoid about my knowledge and skills being stale. So I have to invest my time and effort on updating and expanding my knowledges constantly. With all these new technologies and frameworks popping up all the time, I feel like a headless chook all the time.&lt;/p&gt;
&lt;p&gt;The best way to learn a new language/framework/library is to actually use it. 'Hello World' just doesn't cut it. Yet a full blown application such as Web Mail or Pet Shop may be too big or too long for the purpose of learning or proof of concept. That is why I have my own little pet project - &lt;a href="http://romenlaw.blogspot.com/2008/12/gwt-vs-zk-sloc-count.html"&gt;Address &lt;/a&gt;(among others), which serves as the use cases for my learning and experimentation.&lt;/p&gt;
&lt;p&gt;For the products that I like and feel useful in my work, I will keep them in my toolbelt and probably move them up to the head. For the ones that are irrelavent to me, I drop them from the Long Tail picture altogether. There are also products that have died (e.g. Orion Server) or dying and being replaced by others (e.g. GWT-Ext) in the game of evolution. But it does not mean that my time spent on them were wasted. I still learn and accumulate a great deal of experiences so that when I encounter a new product, I can always draw on these experiences and view it in a better light.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8251396591165443226?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8251396591165443226/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8251396591165443226' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8251396591165443226'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8251396591165443226'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/12/long-tail-theory.html' title='The Long Tail Theory'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-6189913414062757935</id><published>2008-12-01T17:13:00.006+11:00</published><updated>2008-12-08T10:02:21.781+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GWT'/><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>GWT vs. ZK - SLOC Count</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;I often read about how compact the ZK code was compared to GWT. I couldn't quite believe it. I thought it must have been different design patterns and style of coding used. So I decided to do it myself - I implemented my pet project in both GWT-Ext (GWT 1.4.6 and GWT-Ext 0.9.3 in December 2007) and ZK (ZK 3.5.1 with ZK Studio 0.9.0 in November 2008) and compared the Source Line of Code (SLOC) counts using &lt;a href='http://sunset.usc.edu/research/CODECOUNT/'&gt;Code Count tool&lt;/a&gt;.&lt;br/&gt;&lt;p&gt;Before I show the SLOC comparison, I need to give a brief introduction of the little demo program that I wrote. The program is a GUI to show various capabilities of the Address package, which has a service tier and DAO implementations behind it. The Address package provides a meta-data driven way of managing address records (see &lt;a href="http://romenlaw.blogspot.com/2008/10/erlang-mnesia.html"&gt;here &lt;/a&gt;for the data model). The concerned application here is only the GUI portion. The GUI has the following screens (left=ZK, right=GWT):&lt;/p&gt;
&lt;h2&gt;CRUD&lt;/h2&gt;
&lt;p&gt;This screen is used for Create, Retrieve, Update and Delete (CRUD) of the Address records from/to the database.&lt;/p&gt;
&lt;img src='http://frs55g.blu.livefilestore.com/y1pdEN-ZO20KxoldZO8wkSGQx2XnKwOIKUVIWuhuZVNjT3CZg92Eb6T6TBNe2uNjeNrkJA9RkPJRWs/address_zk_crud.png'/&gt;
&lt;img src='http://4qarqw.blu.livefilestore.com/y1p3AC7q1-oZX2FfjrXHtmccYxhUfPFN85wM025uTaDbCk9xwPn5KnR6RtxmKVxZrfz9_IokfbhnHUyFu2ylbqLXg/gwt_crud.png'/&gt;
&lt;h2&gt;Find&lt;/h2&gt;
&lt;p&gt;This screen is used to call the corresponding Address services to find addresses either by keywords or by location.&lt;/p&gt;
&lt;img src='http://frs55g.blu.livefilestore.com/y1pdEN-ZO20KxqiGZR1XhY60nqqVUYmOXw0Jx18LyVgzVjG3FnHszxG_yDv2JrtdQo9oOI4FaLuPiM/address_zk_find.png'/&gt;
&lt;img src='http://4qarqw.blu.livefilestore.com/y1pEHvx5mmPlexZWL3GZ_-UWSIESUtlKQ8BK3OMXo8xef4YLzZ3CtZqcfZgQihJ0ORowTtIQDv-kRact4tcc21hOg/gwt_find.png'/&gt;
&lt;h2&gt;Compare&lt;/h2&gt;
&lt;p&gt;This screen is used to call the corresponding Address services to compare two given Address records. Note that the ZK implementation supports drag-and-drop - i.e. I can drag a row from the list of addresses and drop it onto the text box and the text box will be populated with the content of the address (as a string). On the other hand, the GWT implementation does not support DnD because at the time of writing it, GWT-Ext did not support this kind of DnD yet (and I am not about to rewrite it using new version of GWT-Ext now considering Sanjiv has left the project... but that's another story).&lt;/p&gt;
&lt;img src='http://frs55g.blu.livefilestore.com/y1puFtLnkGGRJTmuoYZHjBPbALTOyx-UsGlPc1tw6qds626PLD-WyWfdTUq1V28nJy-wBTJ0XKym9o/address_zk_compare.png'/&gt;
&lt;img src='http://4qarqw.blu.livefilestore.com/y1pji2PR4HltuKgjZAu9Za4x5sOguT8ONyNkGfYPNjKT-Ib72xJjFmfmuxs6VAgQBKT_5GyFvWs1V4/gwt_compare.png'/&gt;
&lt;h2&gt;Preferences&lt;/h2&gt;
&lt;p&gt;This screen is used to manage user preferences of the GUI application. Cookies are used as the storage.&lt;/p&gt;
&lt;img src='http://frs55g.blu.livefilestore.com/y1pqk8oGAlvNEBW6XJ9PojoLpMGVZs86iq5AIo-vFXLsFwbmX7W5qNtbDtLQU6Rn5So3lNYNYikuyA/address_zk_pref.png'/&gt;
&lt;img src='http://4qarqw.blu.livefilestore.com/y1pwCUD2nQ-CpJRAs4QHl8yr25zl-FNHOG6uIvl5ApPxhhylZox_LJj8RCf9APAiakRvE1vH_vqKzY/gwt_pref.png'/&gt;
&lt;p&gt;In my SLOC count, I divided the source code into the following groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Main View - this includes the main screens: CRUD, Find and Compare tab panels. &lt;/li&gt;
&lt;li&gt;Preference View - the user preference window&lt;/li&gt;
&lt;li&gt;Model/Controller - the code behind the screen components, including models, event handlers, interaction with the service tier, helper/utilities&lt;/li&gt;
&lt;li&gt;Splasher - this is the Splasher screen displayed at the beginning of the application. Only ZK version has implemented this (using &lt;code&gt;index.zul&lt;/code&gt; and automatically forwarding to the main view after initialising the Address service); other versions do not have this screen.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that in ZK, all the views are a result of drag-and-drop from the components palette with modifications by hand.&lt;/p&gt;
&lt;p&gt;The physical and logical SLOC counts are shown below.&lt;/p&gt;
&lt;img src='http://frs55g.blu.livefilestore.com/y1prCkQmPNnOuIq7xakinYlff49j1js2eSDClD0F6_ohEiHRKy6s8GVIbx3wZu6Zx_jqiYCv2tM3nw/address_zk_physical.png'/&gt;
&lt;img src='http://frs55g.blu.livefilestore.com/y1pQcJtY7xUWKtP8BM08Db1Z8BXertapm5DNwMDro7gKF8fCbbTLMB_OmviuBVUxFQ82DWhMjoQtjA/address_zk_logical.png'/&gt;
&lt;p&gt;The results speak for themselves.&lt;/p&gt;
&lt;span style="font-weight:bold;"&gt;UPDATE [2008-12-08]&lt;/span&gt;:
&lt;p&gt;
&lt;ul&gt;
&lt;li&gt;The GWT implementation source code can be found here: &lt;a href="http://dtt0ua.blu.livefilestore.com/y1pqlaV09WwzbpqOl5MtNTXG3a1k-q7lzUd2aWScq9-kgF4c69MjTx1wJMVni7R9xV7iAEb2SYniX8/Address_gwt.zip?download"&gt;Address_gwt.zip&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The ZK implementation source code can be found here: &lt;a href="http://dtt0ua.blu.livefilestore.com/y1pcPMRqLcB0elPIoCEMVszf-l8D-CNeVxKHbWbraiq6nblHJFITUjQwdKUpu9fkxhh7M9PHx3OsSM/Address_zk.rar?download"&gt;Address_zk.rar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;
GWT/GWT-Ext screenshots have also been added after the ZK ones (i.e. GWT screenshots are on the right-hand side).
&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-6189913414062757935?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/6189913414062757935/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=6189913414062757935' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6189913414062757935'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6189913414062757935'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/12/gwt-vs-zk-sloc-count.html' title='GWT vs. ZK - SLOC Count'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1697684486484491493</id><published>2008-11-27T15:10:00.002+11:00</published><updated>2008-11-27T15:16:10.667+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ZK'/><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>ZK - First Impression</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;I first heard of &lt;a href='http://www.zkoss.org'&gt;ZK &lt;/a&gt;from several developer forums, where one of the authors of ZK posted articles comparing ZK with GWT trying to prove that ZK was superior. Ever since then, I have been wanting to learn/use ZK for several months now.&lt;/p&gt;
&lt;p&gt;I was pleasantly surprised by the maturity and high quality of ZK from the first moment I started using it - I didn't expect it to have a drag-and-drop graphical editor - something that not many free web frameworks have out of the box. I believe it is a real contender of GWT. &lt;/p&gt;
&lt;p&gt;The praises of ZK can be found easily and I will not repeat them here. Instead, I will outline the reservations that I have about ZK hoping to get advices from the community.&lt;/p&gt;
&lt;h2&gt;ZK Studio Installation&lt;/h2&gt;
&lt;p&gt;When I followed download instructions on ZK site to install the ZK Studio directly from Eclipse, it turned out to be an older version of the plugin. It took me a while to find out why my ZK Studio did not have the ZUL Graphical Editor mentioned in the documentation. I had to download the plugin (v0.9.x) and install it onto Eclipse manually.
&lt;/p&gt;
&lt;h2&gt;ZUL Graphical Editor&lt;/h2&gt;
&lt;p&gt;The ZUL Graphical Editor is not really an editor at the moment. It only serves as a previewer. I believe ZK team is actively working on improving the editor right now.&lt;/p&gt;
&lt;h2&gt;Debugging&lt;/h2&gt;
I am not aware of any special debugger for ZK. If something goes wrong, I have to inspect the web server's log file to get the stack trace - cannot jump to the line of source code automatically. Moreover, if the error occurs at the ZUL script, the trace shows BeanShell exceptions, which can be harder to locate the real position of the error in the ZUL file.
&lt;h2&gt;Centralised ZScript File&lt;/h2&gt;
I understand that zscript can be isolated in its own file and be included into the .zul file, so that the script is not littered in the presentation code. However, I find that to completely separate the script and the ZUL code is not practical. For example, to specify the a renderer for a component, I need to instantiate the renderer and assign it to a variable, then put the variable to the components renderer attribute. For example,
&lt;pre class='xml' name='code'&gt;
&amp;lt;zscript&amp;gt;
import com.laws.address.zk.*;

renderer=new AddressRenderer();
&amp;lt;/zscript&amp;gt;
   &amp;lt;listbox id="crudAddressList" mold="paging" pageSize="10" 
   itemRenderer="${renderer}"&amp;gt;
    &amp;lt;listhead sizable="true"&amp;gt;
     &amp;lt;listheader label="ID" width="30px"/&amp;gt;
     &amp;lt;listheader forEach="${afts}" &amp;gt;
      &amp;lt;attribute name="label"&amp;gt;${each.name}&amp;lt;/attribute&amp;gt;
     &amp;lt;/listheader&amp;gt;
    &amp;lt;/listhead&amp;gt;
   &amp;lt;/listbox&amp;gt;
&lt;/pre&gt;
&lt;p&gt;However, the bit of script that instantiates and assigns the new renderer cannot be separated into another file (unless a function is created to do it, which looks odd and impractical). &lt;/p&gt;
&lt;h2&gt;Event Handling&lt;/h2&gt;
&lt;p&gt;The event handling in ZK is strange - the sender has to specify the target for the event. The whole idea of events is so that the sender (or publisher) is not aware of who is going to consume the event, or how many consumers, and what the consumer will do with it. But in ZK, the target of the event is specified up front by the sender - i.e. the sender knows who should receive the event. I shall elaborate this point in another post in more detail.&lt;/p&gt;
&lt;p&gt;To be fair, ZK does allow the target of the event to be null when &lt;b&gt;posting &lt;/b&gt;an event. However, the event is only accessible from top-level components in this scenario.&lt;/p&gt;
&lt;h2&gt;Server Round Trips for Static Contents&lt;/h2&gt;
&lt;p&gt;Unlike GWT, even static contents in ZK will involve server decision (a bit like Echo 2). For example, upon clicking a button or menu, my application pops up an 'About' message box displaying static contents. The processing of the button click and the display of the message box involves the server and incurs a round-trip to the server. This certainly has performance penalty and puts presentation logic and load onto the server (which, IMO should be placed in the client tier - i.e. the browser). I know, there has been debate about whether the GWT way is better. I personally prefers the GWT way - especially considering it is broadband era now.
&lt;/p&gt;
&lt;p&gt;Overall, my experience with ZK has been quite positive. I think it is among my top 3 web RIA frameworks.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1697684486484491493?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1697684486484491493/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1697684486484491493' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1697684486484491493'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1697684486484491493'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/11/zk-first-impression.html' title='ZK - First Impression'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8918253262106895942</id><published>2008-11-22T14:55:00.002+11:00</published><updated>2008-11-22T15:00:17.573+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='N95'/><title type='text'>N95 Must Haves: Panoman</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;I travel quite a bit due to the nature of my job. I used to bring my camera along on every trip. But due to &lt;a href='http://romenlaw.blogspot.com/2008/07/ouch-ouch-ouch.html'&gt;my forgetful nature&lt;/a&gt;, and the bulkiness of the normal cameras, I stopped using them on my trips. Instead, I began to use my mobile phone camera, especially after I got my N95.&lt;/p&gt;
&lt;p&gt;N95 must have one of the best phone cameras around. I feel that it is even better than many of the cheap digital cameras. It has a decent Carl Zeiss lens, 5M pixels resolution and intelligent image processing software built-in.&lt;/p&gt;
&lt;p&gt;When traveling, many of my photos are scenery. Panorama pictures are the best to capture the landscape and the atmosphere of the new places that I visit. So far the best panorama software I found is &lt;a href='http://www.panoman.net/archives/category/panoman-explained'&gt;Panoman&lt;/a&gt;. It is extremely easy to use: simple press start and move the handset horizontally, then press stop when you are done. The rest is handled by the Panoman software. Most importantly, the result is superb.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8918253262106895942?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8918253262106895942/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8918253262106895942' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8918253262106895942'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8918253262106895942'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/11/n95-must-haves-panoman.html' title='N95 Must Haves: Panoman'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1616755534326138731</id><published>2008-11-07T12:33:00.003+11:00</published><updated>2008-11-18T21:35:54.442+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><title type='text'>What Do VB &amp; Applet Have In Common?</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;No, I am not talking about Victoria Bitter, which I quite like. I am talking about Visual Basic and Java Applets. They both have a bad reputation due to historical reasons, which is hampering their adoption.&lt;/p&gt;
&lt;p&gt;In the heyday of IT, VB was probably the most widely used programming language at least in the Windows world. IT professionals and IT professional wannabes all flocked to write software to cash in on the golden era of IT. I still remember one of the managers at Australian Taxation Office (ATO) initiated an IT project and wrote the software in VB/Visual Foxpro by himself together with a public servant who had "13 years of programming experience" but also had trouble finding keys on the keyboard.&lt;/p&gt;
&lt;p&gt;Today, VB has become VB.NET and the developer community is more mature. Still, there has been much discussion about why people perceive VB.NET to be inferior to C#. Many people choose C# over VB or even move from VB to C# simply because it will make them look better or making them feel better about themselves - real programmers use 'C-like' languages.&lt;/p&gt;
&lt;p&gt;Unlike VB, the Java Applet never reached popularity since the beginning because those were the dial-up days. I can see three major hurdles for Java Applet in those days: slow internet links meaning downloading the JAR files was a big problem; 'complexity' in installing the JRE plug-in; awkward look-and-feel and lack of good frameworks.&lt;/p&gt;
&lt;p&gt;Today, things have drastically improved: pervasive broadband access, improved browser plug-in capabilities, 80% new PCs are pre-installed with JRE, improved JRE (6 update 10), the imminent release of JavaFX 1.0 and more other applet-based frameworks. Still people doubt whether these frameworks can compete with Silverlight and Flex. I don't see the word 'applet' get mentioned much by Sun or JavaFX's promoters. The FUD is just too deeply entrenched in people's minds.&lt;/p&gt;
&lt;p&gt;At the end of the day, great technology does not thrive on its own merit. The human factor has the final say. It's a bit like Wall Street.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1616755534326138731?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1616755534326138731/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1616755534326138731' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1616755534326138731'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1616755534326138731'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/11/what-do-vb-applet-have-in-common.html' title='What Do VB &amp;amp; Applet Have In Common?'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-6164337526666735125</id><published>2008-11-03T18:02:00.010+11:00</published><updated>2008-11-10T21:01:53.319+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Design'/><title type='text'>A Musical Note</title><content type='html'>&lt;p&gt;I spent last month at home on holiday (is that an oxymoron?) feeling lethargic as people do on holidays. As a result I did not touch my computer much and only spent a few hours &lt;a href="http://romenlaw.blogspot.com/2008/10/erlang-mnesia-take-2.html"&gt;learning Erlang&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;However, I did get to learn music and ended up spending 1-2 hours per day in front of the electronic keyboard that I bought for my kids &lt;img class="inlineimg" title="Wink" alt="" src="http://www.sunniforum.com/forum/images/smilies/icon_wink.gif" border="0" /&gt;. I have to admit, I am not good at music at all - I did use to excel in singing in primary school; but since my voice broke I lost all enthusiasm on music - my parents sending me to a violin class where I was the only boy did not help either. Until last month, as I fiddled around with the piano/keyboard, I revived my thirst for knowledge on music and piano-playing. It turned out that piano-playing is really easy to learn (at least to start) if you are a fast touch-typist. So I relearned the basic music theory 101 by following the wonderful book &lt;a href="http://www.oztion.com.au/vshops/item.aspx?itemid=4279448&amp;tid="&gt;Total Piano&lt;/a&gt;. As an IT professional geek, I couldn't help finding many striking similarities between the musical and computing worlds.&lt;/p&gt;
&lt;p&gt;First of all there are the patterns in music. As a matter of fact, all musical pieces are based on establishing some patterns and then repeating them - there are quite a few symbols in the staff to represent repetitions (i.e. the GOTO statement). It seems like composers are lazier than system designers &lt;img class="inlineimg" title="Wink" alt="" src="http://www.sunniforum.com/forum/images/smilies/icon_wink.gif" border="0" /&gt; Also, there are chords and each chord has many variations - broken, harmonic, minor, etc. Again, it's all about repetition and reusing.&lt;/p&gt;
&lt;p&gt;Then there is the &lt;i&gt;arrangements&lt;/i&gt;. The same music can be (re)arranged to adapt to different environments/scenarios - e.g. to suit different musical instruments (e.g. piano solo, string quartet), or to suit different players (e.g. beginner vs. pro) etc. Thanks to simple arrangements of many great works included in the book, I was able to play some very interesting pieces and keep getting fully engaged in the learning process. By the way, how many AJAX'ian web frameworks have you come across recently?&lt;/p&gt;
&lt;p&gt;Well-known musicians also remake other people's songs to boost their own career. Yesterday, I read an interview with &lt;a href="http://www.seal.com/"&gt;Seal &lt;/a&gt;and his upcoming new album of remakes of classics that 'appealed' to him. In it, Seal revealed that quite a few young people in their 20s had not heard the original version of the songs. So Seal's version was the first time that they heard them and Seal kind of re-introduced the great classics back to the young generations. I hope the youngsters don't mistake Madona's American Pie with the original. &lt;/p&gt;
&lt;p&gt;Every now and then I can hear curses coming from across the office when my colleagues cannot find some features in their MS Office 2007. I have mixed feelings about the ribbon interface of Office 2007 - I like the look and feel but hate the fact that so many features have been rearranged so that everytime I want to use them it's Easter all over again. I am sure if someone has never used the older versions, he/she will like the new ribbon interface.&lt;/p&gt;
&lt;p&gt;Perhaps learning from the closed-door design experiences, Microsoft is openning up their design process for some of their major products - e.g. &lt;a href="http://blogs.msdn.com/e7/"&gt;Enginnering Windows 7&lt;/a&gt;. On the other hand, music creation is usually an extremely personal experience, unless you are charged to write some propaganda piece or the national anthem (I will vote for Waltzing Matilda anytime for Aussie national anthem)!&lt;/p&gt;
&lt;p&gt;Meanwhile, I'd better get back to practise the beautiful piano solo arrangement of Beijing 2008 Olympic theme song - &lt;a href="http://lxp20035.blog.163.com/music/diy/entry/fks_080066092082081074086074094066081094087070092/"&gt;You and Me&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-6164337526666735125?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/6164337526666735125/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=6164337526666735125' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6164337526666735125'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/6164337526666735125'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/11/musical-note.html' title='A Musical Note'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1249510208385610294</id><published>2008-10-21T17:57:00.006+11:00</published><updated>2008-10-21T20:38:57.695+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Database'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='ORM'/><title type='text'>Erlang Mnesia - Take 2</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;After &lt;a href='http://romenlaw.blogspot.com/2008/10/erlang-mnesia.html'&gt;my first attempt&lt;/a&gt; of using Erlang Mnesia, I decided to apply what I have learned so far to refactor and improve my Erlang Mnesia implementation of the Address database and Data Access Layer (DAL).&lt;/p&gt;
&lt;h2&gt;Redesigning The Database&lt;/h2&gt;
This time I de-normalised part of the database design - 
&lt;ol&gt;
&lt;li&gt;to duplicate the &lt;code&gt;hierarchy_order&lt;/code&gt; field from &lt;code&gt;address_field_type&lt;/code&gt; to &lt;code&gt;address_field&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;removed the record id fields from the 3 entities:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;address_field_type&lt;/code&gt; is a reference data table with small amount of data. I use the &lt;code&gt;locale_country&lt;/code&gt; + &lt;code&gt;hierarchy_order&lt;/code&gt; as a combined key implicitly&lt;/li&gt;
&lt;li&gt;&lt;code&gt;address&lt;/code&gt; and &lt;code&gt;address_field&lt;/code&gt; both have the &lt;code&gt;location_code&lt;/code&gt; field which is unique for each record, so I used &lt;code&gt;location_code&lt;/code&gt; as their primary key&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
The &lt;code&gt;address.hrl&lt;/code&gt; file:
&lt;pre class='c#' name='code'&gt;
-record(address_field_type, {
 locale_country,
 name,
 default_value,
 hierarchy_order,
 display_order,
 validation_rules,
 suffix} ).
 
-record(address_field, {
 location_code,
 value,
 locale_country,  %% denormailsed from address_field_type
 hierarchy_order  %% denormalised from address_field_type
 }).
 
-record(address, {
 location_code,
 status
 }).
 
-record(address_addressField, {
 address_locationCode,
 address_field_locationCode
 }).
&lt;/pre&gt;
Creating the database:
&lt;pre name="code" class="c#"&gt;
create_db() -&amp;gt;
 mnesia:create_table(address_field_type,
  [
   {type, bag}, %% use bag because no single-filed primary key now
   {ram_copies,[nonode@nohost]},
   {index, [hierarchy_order]},
   {attributes, record_info(fields, address_field_type)}
  ]),
 mnesia:create_table(address_field,
  [
   {ram_copies,[nonode@nohost]},
   {attributes, record_info(fields, address_field)}
  ]),
 mnesia:create_table(address,
  [
   {ram_copies,[nonode@nohost]},
   {attributes, record_info(fields, address)}
  ]),
 mnesia:create_table(address_addressField,
  [
   {type, bag},
   {ram_copies,[nonode@nohost]},
   {index, [address_field_locationCode]},
   {attributes, record_info(fields, address_addressField)}
  ]).
&lt;/pre&gt;
&lt;p&gt;The above changes do reduce the functionality of the Address application very slightly but it's a worthy trade-off as it simplifies the design - now I don't need the &lt;code&gt;oid&lt;/code&gt; table/record any more along with all the functions and code revolving the oids.&lt;/p&gt;  
&lt;p&gt;Also, by de-normialising, i.e. duplicating the &lt;code&gt;hierarchy_order&lt;/code&gt; field into &lt;code&gt;address_field&lt;/code&gt; table, the sorting of the &lt;code&gt;address_field&lt;/code&gt; list is made easy and efficient (without having to look up the database in every iteration). This is demonstrated in the code below.&lt;/p&gt;  
&lt;h2&gt;Simplified Sorting&lt;/h2&gt;  
The sorting of &lt;code&gt;address_field&lt;/code&gt; list has reduced from 3 functions previously to the use of &lt;code&gt;lists:keysort/2&lt;/code&gt; due to the duplication of the &lt;code&gt;hierarchy_order&lt;/code&gt; field from &lt;code&gt;address_field_type&lt;/code&gt; into address_field in the data/entity model.
&lt;pre name="code" class="c#"&gt;
sort_afs(Afs) when is_list(Afs) -&amp;gt;
 %% 5th field of the address_field tuple is hierarchy_order
 lists:keysort(5, Afs).
&lt;/pre&gt;
&lt;h2&gt;Simplified Data Insertion&lt;/h2&gt;
By skipping any record id handling, the code for insertion of records is considerably simplified. This is most evident from the &lt;code&gt;insert_address/2&lt;/code&gt; function (compared to the &lt;a href="http://romenlaw.blogspot.com/2008/10/erlang-mnesia.html"&gt;previous version&lt;/a&gt;):
&lt;pre name="code" class="c#"&gt;
insert_aft(Aft) when is_record(Aft, address_field_type) -&amp;gt;
 Fun = fun() -&amp;gt;
  mnesia:write(Aft)
 end,
 mnesia:transaction(Fun),
 Aft.
 
insert_address(A, Afs) when is_record(A, address) and is_list(Afs) -&amp;gt;
 {NewA, NewAfs} = address:generate_location_code(A, Afs),
 Fun = fun() -&amp;gt;  
  % create the new address record
  mnesia:write(NewA),
  
  % now insert/update into address_field 
  % and insert into address_addressField table
  lists:foreach( fun(Af) -&amp;gt;
    mnesia:write(Af),
    A_Af=#address_addressField{ 
     address_locationCode=NewA#address.location_code,
     address_field_locationCode=Af#address_field.location_code
    },
    mnesia:write(A_Af)
   end,
   NewAfs),
  {NewA, NewAfs}
 end,
 mnesia:transaction(Fun).
&lt;/pre&gt;
&lt;h2&gt;Simplified Data Queries&lt;/h2&gt;  
By using &lt;code&gt;dirty_read&lt;/code&gt; and &lt;code&gt;dirty_index_read&lt;/code&gt; functions unnecessary transactions are avoided.  
&lt;pre name="code" class="c#"&gt;
find_afts(LocaleCountry) when is_list(LocaleCountry) -&amp;gt;
 mnesia:dirty_read({address_field_type, LocaleCountry}).

find_address(Id) when is_integer(Id) -&amp;gt;
 [A] = mnesia:dirty_read({address, Id}),
 A.
 
find_address_field(Id) when is_integer(Id) -&amp;gt;
 [Af] = mnesia:dirty_read({address_field, Id}),
 Af.
 
find_address_field_codes(A_code) -&amp;gt;
 % the read will return a list of tuples, we want the 3rd field (the AF_locationCode) 
 % of each tuple and put them in a list.
 [ element(3, A) || A &amp;lt;- mnesia:dirty_read({address_addressField, A_code}),
  true ].
 
find_address_codes(Af_code) -&amp;gt;
 F = fun() -&amp;gt;
  mnesia:select(address_addressField, [{
   #address_addressField{ address_locationCode='$1',
    address_field_locationCode=Af_code, 
    _='_' },
    [], ['$1'] }] 
  )
 end,
 {atomic, Results}=mnesia:transaction(F),
 Results.
 
find_address_codes2(Af_code) -&amp;gt;
 F = fun() -&amp;gt;
  Q = qlc:q([Aaf#address_addressField.address_locationCode
   || Aaf &amp;lt;- mnesia:table(address_addressField),
   Aaf#address_addressField.address_field_locationCode==Af_code]),
  qlc:e(Q)
 end,
 {atomic, Results}=mnesia:transaction(F),
 Results.
 
find_address_codes3(Af_code) -&amp;gt;
 Aafs = mnesia:dirty_index_read(address_addressField, Af_code, 
  #address_addressField.address_field_locationCode),
 % the second element of the tuple is the address_locationCode
 [ element(2, Aaf) || Aaf &amp;lt;- Aafs, true ].
&lt;/pre&gt;
In the above code, &lt;code&gt;find_address_codes/1&lt;/code&gt;, &lt;code&gt;find_address_codes2/1&lt;/code&gt;, &lt;code&gt;find_address_codes3/1&lt;/code&gt; do the same thing but are implemented using different approaches. The dirty read one is the simplest.
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Just by thinking in Erlang, the resulting database design has been simplified - less table and fields; the source lines of code (SLOC) has been reduced by 30%; most importantly, the programming logic is now simpler and easier to read/maintain.&lt;/p&gt;
&lt;p&gt;However, this does not mean that Erlang and Mnesia are the best way to implement this kind of database applications (where normalisation is important for the business logic).&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1249510208385610294?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1249510208385610294/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1249510208385610294' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1249510208385610294'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1249510208385610294'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/10/erlang-mnesia-take-2.html' title='Erlang Mnesia - Take 2'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-9043770036261428481</id><published>2008-10-18T22:35:00.008+11:00</published><updated>2008-12-09T13:14:36.401+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Database'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='ORM'/><title type='text'>Erlang Mnesia</title><content type='html'>&lt;p&gt;Carrying out my Erlang learning plan, I embarked on the &lt;a href="http://romenlaw.blogspot.com/2008/10/erlang-odbc.html"&gt;porting exercise of my Address DAO package&lt;/a&gt; into Erlang Mnesia. In fact, it is more than porting of the data access functions. It is also a porting of the Address database itself from RDBMS into Mnesia.&lt;/p&gt;
&lt;h2&gt;Creating the Mnesia Schema&lt;/h2&gt;
&lt;p&gt;The Entity Relationship Diagram (ERD) of the database schema is shown below.&lt;/p&gt;
&lt;a href="http://dtt0ua.blu.livefilestore.com/y1p8JYYY_9op6qTM9-pbT9xgMXlKDhOceVJMSeVh6Mdbm-iBGnwqLZaA9kHKfuvNDdcPaofjhIQUAE/ERD.PNG"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px;" src="http://dtt0ua.blu.livefilestore.com/y1p8JYYY_9op6qTM9-pbT9xgMXlKDhOceVJMSeVh6Mdbm-iBGnwqLZaA9kHKfuvNDdcPaofjhIQUAE/ERD.PNG" border="0" alt="" /&gt;&lt;/a&gt;
&lt;p&gt;To create the tables in Mnesia, I defined the following records, each corresponding to a table.&lt;/p&gt;
&lt;pre name="code" class="python"&gt;
-record(address_field_type, {id,
 name,
 locale_country,
 default_value,
 hierarchy_order,
 display_order,
 validation_rules,
 suffix} ).
 
-record(address_field, {id,
 value,
 address_field_type_id,
 location_code}).
 
-record(address, {id,
 status,
 location_code}).
 
-record(address_addressField, {
 address_id,
 address_field_id
 }).
-record(oids, {
 name,
 id
 }).
&lt;/pre&gt;
&lt;p&gt;Note that there is an extra record &lt;code&gt;oids&lt;/code&gt;. This record/table is required because Mnesia does not support auto-incrementing fields usually used as primary key generation. Therefore this extra table is used to store the current key value - similar to Oracle's sequence.&lt;/p&gt;
&lt;p&gt;Creating the tables are shown below. The &lt;code&gt;oids&lt;/code&gt; table is also populated with seed values.&lt;/p&gt;
&lt;pre name="code" class="python"&gt;
create_db() -&gt;
 mnesia:create_table(address_field_type,
  [
   {ram_copies,[nonode@nohost]},
   {attributes, record_info(fields, address_field_type)}
  ]),
 mnesia:create_table(address_field,
  [
   {ram_copies,[nonode@nohost]},
   {index, [location_code]},
   {attributes, record_info(fields, address_field)}
  ]),
 mnesia:create_table(address,
  [
   {ram_copies,[nonode@nohost]},
   {index, [location_code]},
   {attributes, record_info(fields, address)}
  ]),
 mnesia:create_table(address_addressField,
  [
   {type, bag},
   {ram_copies,[nonode@nohost]},
   {index, [address_field_id]},
   {attributes, record_info(fields, address_addressField)}
  ]),
 mnesia:create_table(oids,
  [
   {attributes, record_info(fields, oids)}
  ]),
 Fun = fun() -&gt;
  mnesia:write(#oids{ name=address, id=0 }),
  mnesia:write(#oids{ name=address_field_type, id=0 }),
  mnesia:write(#oids{ name=address_field, id=0})
 end,
 mnesia:transaction(Fun).
&lt;/pre&gt;
&lt;h2&gt;Simple CRUD&lt;/h2&gt;
The &lt;code&gt;address_field_type&lt;/code&gt; is a table for storing reference data, which does not rely on other tables. So the C and R of the CRUD is very simple. From &lt;code&gt;address_db.erl&lt;/code&gt; file:
&lt;pre name="code" class="python"&gt;
%% returns {atomic, Oid} or {aborted, Reason}
generate_oid(TableName) when is_atom(TableName) -&gt;
 Fun = fun() -&gt;
  [Oid] = mnesia:read(oids, TableName, write),
  %% because Erlang only supports single assignment
  %% I have to create new variables every time the value changes.
  NewId=Oid#oids.id+1,
  New = Oid#oids{ id=NewId },
  mnesia:write(New),
  NewId
 end,
 mnesia:transaction(Fun).
  
insert_aft(Aft) when is_record(Aft, address_field_type) -&gt;
 Fun = fun() -&gt;
  {atomic, Id}=generate_oid(address_field_type),
  New = Aft#address_field_type{ id=Id },
  mnesia:write(New),
  New
 end,
 mnesia:transaction(Fun).

find_aft(Id) when is_integer(Id) -&gt;
 {atomic, [Aft]} = mnesia:transaction(fun()-&gt; mnesia:read({address_field_type, Id}) end),
 Aft.
&lt;/pre&gt;
There are a few things worth noting:
&lt;ol&gt;
&lt;li&gt;Although the &lt;code&gt;mnesia:read&lt;/code&gt; and &lt;code&gt;mnesia:select&lt;/code&gt; functions can be read-only, they still need to be wrapped in a function and called by &lt;code&gt;mnesia:transaction/1&lt;/code&gt;. This does not make sense to me.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;mnesia:transaction(Fun)&lt;/code&gt; returns &lt;code&gt;{aborted, Reason}&lt;/code&gt; or &lt;code&gt;{atomic, Result}&lt;/code&gt; where &lt;code&gt;Result&lt;/code&gt; is whatever the return result of the &lt;code&gt;Fun&lt;/code&gt; is. Therefore, the &lt;code&gt;insert_aft/1&lt;/code&gt; above returns &lt;code&gt;New&lt;/code&gt; - the new &lt;code&gt;address_field_type&lt;/code&gt; record with the &lt;code&gt;id&lt;/code&gt; field populated.&lt;/li&gt;
&lt;li&gt;Erlang is &lt;b&gt;single assignment&lt;/b&gt; language - i.e. a variable can only be assigned value once. Therefore, if I want to assign the &lt;code&gt;Aft#address_field_type.id&lt;/code&gt;, I have to create a whole new &lt;code&gt;address_field_type&lt;/code&gt; record as in the &lt;code&gt;insert_aft/1&lt;/code&gt; above. I have to say, I am not a fan of this single assignment - it defeats the half of the purpose of having variables.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;A Slightly More Complicated CRUD&lt;/h2&gt;
The &lt;code&gt;address&lt;/code&gt; entity relies on &lt;code&gt;address_field&lt;/code&gt;, which in turn depends on &lt;code&gt;address_field_type&lt;/code&gt;. Also, if &lt;code&gt;address&lt;/code&gt; records share the same &lt;code&gt;address_field&lt;/code&gt; values, then the &lt;code&gt;address_field&lt;/code&gt; record should be shared and not be duplicated in the database. Therefore, the management of the &lt;code&gt;address&lt;/code&gt; records are more complicated than those of &lt;code&gt;address_field_type&lt;/code&gt;.
&lt;p&gt;The creation of &lt;code&gt;address&lt;/code&gt; records is shown below.&lt;/p&gt;
&lt;pre name="code" class="python"&gt;
insert_address(A, Afs) when is_record(A, address) and is_list(Afs) -&gt;
 {A2, Afs2} = address:generate_location_code(A, Afs),
 Fun = fun() -&gt;
  % populate the address_field records' id field
  NewAfs=lists:foldl( fun(Af, NewAfs) -&gt;
    % check if the same Af is already in database
    Result = mnesia:select(address_field, [{
     #address_field{ id='$1',
      location_code=Af#address_field.location_code, 
      _='_' },
     [], ['$1'] }] 
    ),
    if length(Result)==0 -&gt; % Af not in DB, so insert it
     {atomic, Id}=generate_oid(address_field);
    true -&gt; % Af already exists in database
     Id=lists:nth(1, Result)
    end,
    NewAf = Af#address_field{ id=Id },
    lists:append(NewAfs, [NewAf])
   end,
   [],
   Afs2),
  
  % create the new address record
  {atomic, AddressId} = generate_oid(address),
  NewA = A2#address{ id = AddressId },
  mnesia:write(NewA),
  
  % now insert/update into address_field 
  % and insert into address_addressField table
  lists:foreach( fun(Af) -&gt;
    mnesia:write(Af),
    A_Af=#address_addressField{ 
     address_id=AddressId,
     address_field_id=Af#address_field.id
    },
    mnesia:write(A_Af)
   end,
   NewAfs),
  {NewA, NewAfs}
 end,
 mnesia:transaction(Fun).
&lt;/pre&gt;
From the &lt;code&gt;address.erl&lt;/code&gt; file:
&lt;pre name="code" class="python"&gt;
%% applying quick sort to AddressField list.
sort_afs([]) -&gt; [];
sort_afs([Pivot|Tail]) when is_record(Pivot, address_field) -&gt;
 sort_afs([Af || Af &lt;- Tail, 
  compare_aft(address_db:find_aft(Af#address_field.address_field_type_id),
   address_db:find_aft(Pivot#address_field.address_field_type_id)) &lt; 0])
  ++ [Pivot] ++
  sort_afs([Af || Af &lt;- Tail, 
  compare_aft(address_db:find_aft(Af#address_field.address_field_type_id),
   address_db:find_aft(Pivot#address_field.address_field_type_id)) &gt;= 0]).

compare_aft(Aft1, Aft2) when 
 is_record(Aft1, address_field_type) and is_record(Aft2, address_field_type) -&gt;
 Aft1#address_field_type.hierarchy_order - Aft2#address_field_type.hierarchy_order.

generate_location_code(A, AddressFields) when is_record(A, address) 
 and is_list(AddressFields) and (length(AddressFields)&gt;0) -&gt;
 Afs=generate_location_code(AddressFields),
 FirstAf=lists:nth(1, Afs),
 { 
  A#address{ location_code=FirstAf#address_field.location_code },
  Afs
 }.

generate_location_code(Afs) -&gt;
 ReverseSortedAfs=lists:reverse(sort_afs(Afs)),
 generate_location_code(ReverseSortedAfs, [], []).
 
generate_location_code([Head|Tail], String, NewAfs) when is_record(Head, address_field) -&gt;
 StringValue = Head#address_field.value ++ String,
 Code=erlang:phash2(StringValue),
 generate_location_code(Tail, 
  StringValue,
  [ Head#address_field{ location_code=Code } ] ++ NewAfs);  
generate_location_code([], _String, NewAfs) -&gt;
 NewAfs.   
&lt;/pre&gt;
The following functions are queries from the &lt;code&gt;address_db.erl&lt;/code&gt; file:
&lt;pre name="code" class="python"&gt;
find_address(Id) when is_integer(Id) -&gt;
 {atomic, [A]} = mnesia:transaction(fun()-&gt; mnesia:read({address, Id}) end),
 A.
 
find_address_field(Id) when is_integer(Id) -&gt;
 {atomic, [Af]} = mnesia:transaction(fun()-&gt; mnesia:read({address_field, Id}) end),
 Af.
 
find_address_field_ids(A_id) -&gt;
 mnesia:transaction(fun()-&gt; mnesia:read({address_addressField, A_id}) end).
 
find_address_ids(Af_id) -&gt;
 F = fun() -&gt;
  mnesia:select(address_addressField, [{
   #address_addressField{ address_id='$1',
    address_field_id=Af_id, 
    _='_' },
    [], ['$1'] }] 
  )
 end,
 mnesia:transaction(F).
 
find_address_ids2(Af_id) -&gt;
 F = fun() -&gt;
  Q = qlc:q([Aaf#address_addressField.address_id 
   || Aaf &lt;- mnesia:table(address_addressField),
   Aaf#address_addressField.address_field_id==Af_id]),
  qlc:e(Q)
 end,
 mnesia:transaction(F).
&lt;/pre&gt;
Things that I want to highlight from the above are:
&lt;ol&gt;
&lt;li&gt;Unlike Java or C#, Erlang does not have a sortable list out-of-the-box. Instead, I had to write functions that sort my list of &lt;code&gt;address_field&lt;/code&gt; records - these are: &lt;code&gt;sort_afs, compare_aft&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;When searching using the primary key field, &lt;code&gt;mnesia:read&lt;/code&gt; is used; when searching using other fields, either use &lt;code&gt;mnesia:select&lt;/code&gt; or &lt;code&gt;QLC&lt;/code&gt;. In the above code, the functions &lt;code&gt;find_address_ids/1&lt;/code&gt; and &lt;code&gt;find_address_ids2/1&lt;/code&gt; do the same thing. However, the result (a list) given by &lt;code&gt;find_address_ids2/1&lt;/code&gt; are reversed.&lt;/li&gt;
&lt;li&gt;If a record with the same primary key already exists in the table, the &lt;code&gt;mnesia:write&lt;/code&gt; either creates (if table type if &lt;code&gt;set&lt;/code&gt;) or updates (if table type is &lt;code&gt;bag&lt;/code&gt;) the record in the table. &lt;/li&gt;
&lt;li&gt;Again, due to the single assignment rule, the &lt;code&gt;lists:append(List, AnotherList)&lt;/code&gt; does not change the List; instead, the new list is the return value of &lt;code&gt;lists:append/2&lt;/code&gt;. This is just counter-intuitive.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
&lt;strong&gt;Related Posts&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/10/erlang-mnesia-take-2.html"&gt;Erlang Mnesia - Take 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-9043770036261428481?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/9043770036261428481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=9043770036261428481' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/9043770036261428481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/9043770036261428481'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/10/erlang-mnesia.html' title='Erlang Mnesia'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5690948122233302694</id><published>2008-10-13T21:29:00.009+11:00</published><updated>2008-10-16T21:53:21.782+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Database'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><category scheme='http://www.blogger.com/atom/ns#' term='ORM'/><title type='text'>Erlang ODBC</title><content type='html'>&lt;p&gt;Continuing &lt;a href="http://romenlaw.blogspot.com/2008/10/dealing-with-databases-in-erlang.html"&gt;my Erlang journey&lt;/a&gt;, I decided to try with Erlang ODBC by porting one of my toy applications. Perhaps not a wise choice since working with database (especially relational database) is not a strength of Erlang, at least for now.&lt;/p&gt;
&lt;p&gt;After almost 10 years and being spoiled by myriad of Object-Relational Mapping (ORM) frameworks, using ODBC again feels like a giant leap backward - 3 generations back to be exact (ADO.Net, ADO, ODBC). So the overall development experience is not that great. This is not to say that Erlang is not a great language, but Erlang as a platform is quite narrow in scope (this makes sense considering Erlang's strong telco heritage).&lt;/p&gt;
&lt;p&gt;So here is my porting exercise. Note that the code below is my feeble attempt at using/learning Erlang by reading the man-pages alone, so if you have suggestions to improve it, please drop a comment. Also, it is not robust since it does not handle error conditions.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;The application (or component) that I am working on is a Data Access Objects (DAO) package which I originally developed in Java using various ORM frameworks and then C# using various 3rd-party ORM and ADO.NET frameworks. The data model is quite simple, consisting 3 entities: AddressFieldType (AFT for short), AddressField (AF for short) and Address. The relationships among them are as following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AFT - AF has a one-to-many relationship&lt;/li&gt;
&lt;li&gt;Address - AF has a many-to-many relationship&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The partial entity relationship diagram (ERD) can be found in &lt;a href="http://romenlaw.blogspot.com/2008/06/entity-framework-bug.html"&gt;my previous blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here, I attempt to implement some of the interfaces from the DAO package using Erlang ODBC.&lt;/p&gt;
&lt;h2&gt;The Design&lt;/h2&gt;
&lt;p&gt;Since Erlang is not an object oriented language, I cannot call my module DAO, so I call it DAL (Data Access Layer) instead borrowing from Microsoft terminology. I have two simple modules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;address_dal&lt;/code&gt;: implementation of some of the interfaces retrieving records from the Address database (using MySQL 5.x with ODBC driver 5.1.5 for win32 downloaded from MySQL).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;orm&lt;/code&gt;: ORM here means ODBC-Record Mapping. There are two types of functions in this module: those that provide ODBC template functions; and those that convert ODBC returned data into Erlang Records defined in &lt;code&gt;address.hrl&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Records&lt;/h2&gt;
&lt;p&gt;I want to work with Erlang records rather than tuples returned by ODBC calls. So I created these records to represent the domain model. From the &lt;code&gt;address.hrl&lt;/code&gt; file:
&lt;pre name="code" class="c#"&gt;
-record(address_field_type, {id,
 name,
 locale_country,
 default_value,
 hierarchy_order,
 display_order,
 validation_rules,
 suffix} ).
 
-record(address_field, {id,
 value,
 address_field_type_id,
 location_code}).
 
-record(address, {id,
 status,
 location_code,
 address_fields = [#address_field{}]}).
&lt;/pre&gt;
&lt;p&gt;Notice the &lt;code&gt;address&lt;/code&gt; record has a list of &lt;code&gt;address_field&lt;/code&gt; records, representing half of the many-to-many relationship.&lt;/p&gt;
&lt;/p&gt;
&lt;h2&gt;ODBC Template&lt;/h2&gt;
&lt;p&gt;I need to surround my SQL queries with database connection and disconnection so that these boiler plate codes do not get scattered everywhere in my business logic. Also, if the SQL queries results in error, Erlang breaks the ODBC connection (I am not sure if this is Erlang's fault or ODBC's). So I have to reconnect before my next SQL query anyway. &lt;/p&gt;
&lt;p&gt;So in my &lt;code&gt;orm.erl&lt;/code&gt; I have these ODBC template/boiler-plate functions:&lt;/p&gt;
&lt;pre name="code" class="c#"&gt;
-define(CONNECTION_STRING, "DSN=Address-MySQL;UID=root").

connect() -&gt;
 case proplists:is_defined(odbc,application:which_applications()) of
  false -&gt;
   application:start(odbc); % pre R12 way of starting odbc;
  _Else -&gt;
   false % do nothing
 end,
 odbc:connect(?CONNECTION_STRING, [
  {auto_commit, off}, 
  {scrollable_cursor, off} 
 ]).
 
sql_query(Query) -&gt;
 {ok, Ref}=connect(),
 ResultTuple = odbc:sql_query(Ref, Query),
 odbc:disconnect(Ref),
 ResultTuple.
 
param_query(Query, Params) -&gt;
 {ok, Ref}=connect(),
 ResultTuple = odbc:param_query(Ref, Query, Params),
 odbc:disconnect(Ref),
 ResultTuple.

&lt;/pre&gt;
&lt;p&gt;Note that Erlang ODBC is based on ODBC v3 which supports connection pooling which has been turned on. So surrounding the SQL queries with connection and disconnection should not incur performance penalty (although I have not explicitly tested it). &lt;/p&gt;
&lt;p&gt;One thing I hate about Erlang's &lt;code&gt;odbc:connect()&lt;/code&gt; is that it does not give any comprehensive error reasons if the connection fails - it always says &lt;code&gt;"No SQL-driver information available. Connection to database failed."&lt;/code&gt; Again, I am not sure if this is Erlang's or ODBC's fault.&lt;/p&gt;
&lt;h2&gt;Converting into Records&lt;/h2&gt;
&lt;p&gt;The ODBC query functions return the results as tuples or list of tuples. I want to convert them into recdords so that I can access the fields of the records more easily - e.g. &lt;code&gt;#address.id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I tried two approaches implementing the record constructor functions. The first one is more portable since it does not rely on the field position but uses column name as a clue to map to the record's fields. This is shown below from the &lt;code&gt;orm.erl&lt;/code&gt; file mapping the &lt;code&gt;AddressFieldType&lt;/code&gt; query results to their corresponding &lt;code&gt;address_field_type&lt;/code&gt; record type:&lt;/p&gt;
&lt;pre name="code" class="c#"&gt;
construct_aft_records(ColNames, Rows) when erlang:is_list(ColNames) and (length(ColNames)&gt;0)
 and erlang:is_list(Rows) -&gt;
 if (length(Rows)&gt;0) -&gt;
  lists:foldl(fun(R, AftRecords) -&gt;
    lists:append(AftRecords, [construct_aft_record(ColNames, R)])
   end,
   [], % initial value of AftRecords list
   Rows
  );
 true-&gt;
  []
 end.

   
construct_aft_record(ColNames, Row) when erlang:is_list(ColNames) and (length(ColNames)&gt;0)
 and erlang:is_tuple(Row) and (size(Row) == length(ColNames)) -&gt;
 Map=lists:foldl(fun(N, Map) -&gt;
  lists:append(Map, 
   [{string:to_upper(lists:nth(N, ColNames)), 
   element(N,Row)}]
  )
  end,
  [], % initial value of Map is []
  lists:seq(1, length(ColNames))
 ),
 #address_field_type{
  id=extract_map_value(Map, "ADDRESSFIELDTYPEID"),
  name=extract_map_value(Map, "NAME"),
  locale_country=extract_map_value(Map, "LOCALECOUNTRY"),
  default_value=extract_map_value(Map, "DEFAULTVALUE"),
  hierarchy_order=extract_map_value(Map, "HIERARCHYORDER"),
  display_order=extract_map_value(Map, "DISPLAYORDER"),
  validation_rules=extract_map_value(Map, "VALIDATIONRULES"),
  suffix=extract_map_value(Map, "SUFFIX")
 }.

 
extract_map_value(Map, Key) -&gt;
  element(2, element(2,lists:keysearch(Key, 1, Map))).
&lt;/pre&gt;
&lt;p&gt;The second approach is lazier as it relies on the field position as specified in the SELECT SQL statement. This is shown below from the &lt;code&gt;orm.erl&lt;/code&gt; file.
&lt;/p&gt;
&lt;pre name="code" class="c#"&gt;
find_addresses(Query, Params) -&gt;
 {ok, Ref}=connect(),
 {selected, _, Rows} = odbc:param_query(Ref, Query, Params),
 % now for each address Row fetch its addressField records
 AddressRecords = lists:foldl(fun(A, Records) -&gt;
   AddressId=element(1,A),
   AddressStatus=element(2,A),
   AddressLocationCode=element(3,A),
   {selected, _, AfRows}=odbc:param_query(Ref,
    "SELECT af.addressFieldId, af.value, af.addressFieldTypeId,"
    "       af.locationCode, aft.hierarchyOrder"
    " FROM Address.addressField as af, Address.AddressFieldType as aft,"
         "      Address.address_addressField as aaf"
    " WHERE aaf.addressId=?"
      "   AND af.addressFieldId=aaf.addressFieldId"
      "   AND aft.addressFieldTypeId=af.addressFieldTypeId"
            " ORDER BY aft.hierarchyOrder",
            [{sql_integer, [AddressId]}]
           ),
           AfRecords = lists:foldl(fun(Af, AddressFieldRecords) -&gt;
             AfRecord=#address_field{
              id=element(1, Af),
      value=element(2, Af),
      address_field_type_id=element(3, Af),
      location_code=element(4, Af)
             },
             lists:append(AddressFieldRecords, [AfRecord])
            end,
            [],
            AfRows
           ),
           AddressRecord= #address{
            id=AddressId,
    status=AddressStatus,
    location_code=AddressLocationCode,
    address_fields=AfRecords
   },
   lists:append(Records, [AddressRecord])
          end,
  [],
  Rows
 ),
 odbc:disconnect(Ref),
 AddressRecords.
&lt;/pre&gt;
&lt;h2&gt;Implementing the DAL Interfaces&lt;/h2&gt;
&lt;p&gt;Now that the boiler-plate code is done, I can implement the DAL interface methods, oops! I mean functions. Here is the &lt;code&gt;address_dal.erl&lt;/code&gt; file.
&lt;pre name="code" class="c#"&gt;
%% Implementation of the Address Data Access Layer (DAL) interfaces.
-module(address_dal).
-export([find_aft/0, find_aft/1]).
-export([find_addresses_in_af/1, find_addresses_in_location/1]).
-include("address.hrl").
 
find_aft() -&gt;
 {selected, ColNames, Rows} = orm:sql_query("SELECT * from Address.AddressFieldType"),
 orm:construct_aft_records(ColNames, Rows).
 
find_aft(LocaleCountry) -&gt;
 {selected, ColNames, Rows} = orm:param_query("SELECT * from Address.AddressFieldType "
  "WHERE localeCountry=?", 
  [{{sql_varchar, 64}, [LocaleCountry]} ]
 ),
 orm:construct_aft_records(ColNames, Rows).


find_addresses_in_af(AddressFieldId) -&gt;
 orm:find_addresses( 
  "SELECT a.addressID, a.status, a.locationCode"
  " FROM Address.Address as a, Address.Address_AddressField as aaf"
  " WHERE aaf.ADDRESSFIELDID = ?"
  " AND a.ADDRESSID=aaf.ADDRESSID",
  [{sql_integer, [AddressFieldId]}] 
 ).
 
% the input parameter LocationCode needs to be a string due to the ODBC 
% to Erlang datatype mapping - hence the test for is_list().
find_addresses_in_location(LocationCode) when is_list(LocationCode) -&gt;
 orm:find_addresses("SELECT a.addressID, a.status, a.locationCode"
  " FROM Address.Address as a, Address.AddressField as af, Address.Address_AddressField as aaf"
  " WHERE af.LOCATIONCODE = ?"
  "   AND a.ADDRESSID=aaf.ADDRESSID"
         "   AND af.ADDRESSFIELDID=aaf.ADDRESSFIELDID",
  [{{sql_numeric, 32, 0}, [LocationCode]}] 
 ).
&lt;/pre&gt;
Notice that in the &lt;code&gt;find_aft&lt;/code&gt; functions I can use &lt;code&gt;'SELECT *...'&lt;/code&gt; because the &lt;code&gt;orm:construct_aft_records/2&lt;/code&gt; function uses the column names to map to the &lt;code&gt;address_field_type&lt;/code&gt; record; on the other hand, the &lt;code&gt;find_addresses...&lt;/code&gt; functions have to use &lt;code&gt;'SELECT a.addressID, a.status, a.locationCode...'&lt;/code&gt; and in that strict order because the &lt;code&gt;orm:find_addresses/2&lt;/code&gt; function expects the columns to be in those positions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5690948122233302694?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5690948122233302694/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5690948122233302694' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5690948122233302694'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5690948122233302694'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/10/erlang-odbc.html' title='Erlang ODBC'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5492127413049156853</id><published>2008-10-09T16:01:00.004+11:00</published><updated>2008-10-13T22:28:17.463+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Database'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Dealing with Databases in Erlang</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;&lt;i&gt;Correction: Thanks to Brandon's comment below, there is also Erlang ODBC which comes with the OTP distribution. This is the 'official' gateway to the RDBMS world. I had a go at it in &lt;a href="http://romenlaw.blogspot.com/2008/10/erlang-odbc.html"&gt;this post&lt;/a&gt;. There is also &lt;a href="http://incubator.apache.org/couchdb/index.html"&gt;CouchDB &lt;/a&gt;from Apache, which is still in incubation.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;Currently there are basically two ways to use database in Erlang - &lt;a href='http://www.erlang.org/doc/apps/mnesia/'&gt;Mnesia &lt;/a&gt;and &lt;a href='http://yarivsblog.com/articles/2006/08/29/introducing-erlydb-the-erlang-twist-on-database-abstraction/'&gt;ErlyDB&lt;/a&gt;. There is also the prospect of edbc. But nothing is available yet from the project.&lt;/p&gt;
&lt;p&gt;Mnesia is the native DBMS in Erlang. It is bundled with Erlang/OTP distribution and is written in Erlang as well. It was originally developed for telco switching applications by Ericsson. Therefore, it boasts a lot of non-functional features - in-memory database, distributed database, high performance (for read), making changes without downtime, etc. However, all these benefits are at the cost of sacrificing many of the basic functionality of a traditional RDBMS - such as referential integrity check, data type validation/check, normalisation support, triggers, stored procedures, etc. &lt;/p&gt;
&lt;p&gt;ErlyDB is essentially Yariv's one-man effort. It's a code generator to generate the data access layer code in Erlang to work with RDBMS (and perhaps Mnesia). Currently supported RDBMS are MySQL and Postgress. It's primary use case was &lt;a href='http://erlyweb.org/'&gt;ErlyWeb &lt;/a&gt;from the same author. It looked promising. However, the whole ErlyWeb and ErlyDB/ErlySQL seem to have lost its momentum: The last ErlyWeb release was over a year ago; ErlyDB is no longer a project on its own right, therefore, no information on its releases; promised features from Yariv's blog have not eventuate after 1 to 2 years.&lt;/p&gt;
&lt;p&gt;So given the database landscape in Erlang, the most viable approach is to use Mnesia if you don't mind the lack of database features and tools. I will give it a try by porting my prototype &lt;a href='http://romenlaw.blogspot.com/2008/06/entity-framework-bug.html'&gt;Address database&lt;/a&gt; into Mnesia.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5492127413049156853?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5492127413049156853/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5492127413049156853' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5492127413049156853'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5492127413049156853'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/10/dealing-with-databases-in-erlang.html' title='Dealing with Databases in Erlang'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7328339094749739662</id><published>2008-10-05T17:04:00.019+11:00</published><updated>2008-10-09T23:08:44.890+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web Service'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='Telecommunications'/><category scheme='http://www.blogger.com/atom/ns#' term='Erlang'/><title type='text'>Consuming RESTful Service with Erlang</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;Having been working in the telco industry for over 10 years, I can't help feeling a bit ashamed for not having learned &lt;a href="http://www.erlang.org/"&gt;Erlang&lt;/a&gt;. Erlang was developed by &lt;a href='http://en.wikipedia.org/wiki/Ericsson'&gt;Ericsson &lt;/a&gt;- the leading Swedish-based network equipment provider, and has been used in many of the network switching equipment produced by Ericsson. Erlang has gained more traction recently, especially in the last year or two.&lt;/p&gt;
&lt;p&gt;Here, I write an Erlang program to consume a demo RESTful service that I developed a couple of months ago. The Erlang code is based on &lt;a href='http://pragdave.pragprog.com/pragdave/2007/04/a_first_erlang_.html'&gt;example by Dave Thomas&lt;/a&gt; - the author of Programming Ruby. The detail of the RESTful service is available in my &lt;a href='http://romenlaw.blogspot.com/2008/08/restful-web-service.html'&gt;previous post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are two services that I want to consume.&lt;/p&gt; 
&lt;p&gt;The first one accepts a social web site URL and scrapes the page for the person's interests in movies and music to return a list of comma-delimited keywords. For example,&lt;/p&gt;
&lt;pre name='code'&gt;
http://localhost:8080/SvdemoRestful/resources/webPageKeywords?url=http://localhost/someWebPage.html
&lt;/pre&gt;
&lt;p&gt;if any keywords are found, then they are returned in the body of the HTTP response as a string; otherwise, the HTTP body is empty. For example,&lt;/p&gt;
&lt;pre name='code'&gt;
folk songs, pop music, chinese music ,battle, action, comedy
&lt;/pre&gt;
&lt;p&gt;Notice the spaces in the above string. The second service I want to consume accepts the above string to search for any matching promotion in a mock up XML database. If any is found then the XML string will be returned in the HTTP body; otherwise, the string &lt;code&gt;&amp;lt;Empty/&amp;gt;&lt;/code&gt; is returned in the HTTP body. For example, the following URL will return the promotion information in XML below:&lt;/p&gt;
&lt;pre name="code"&gt;
http://localhost:8080/SvdemoRestful/resources/promo/jazz
&lt;/pre&gt;
&lt;p&gt;the returned XML:&lt;/p&gt;
&lt;pre name="code" class="xml"&gt;
&amp;lt;Promotion&amp;gt;
  &amp;lt;Code&amp;gt;802&amp;lt;/Code&amp;gt; 
  &amp;lt;Name&amp;gt;Jazz Night&amp;lt;/Name&amp;gt; 
  &amp;lt;Description&amp;gt;Jazz lovers' do not miss this once in a lifetime opportunity.&amp;lt;/Description&amp;gt; 
  &amp;lt;Venue&amp;gt;The Jazz Club&amp;lt;/Venue&amp;gt; 
  &amp;lt;DateTime&amp;gt;2008-10-30 21:00:00&amp;lt;/DateTime&amp;gt; 
  &amp;lt;Tags&amp;gt;Jazz&amp;lt;/Tags&amp;gt; 
&amp;lt;/Promotion&amp;gt;
&lt;/pre&gt;
&lt;p&gt;Now, let's do this in Erlang.&lt;/p&gt;
&lt;p&gt;Create a file called &lt;code&gt;svdemoClient.erl&lt;/code&gt;. The following code will consume the first RESTful service:&lt;/p&gt;
&lt;pre name="code" class="python"&gt;
-module(svdemoClient).
-export([get_keywords/1]).

-define(BASE_URL, "http://localhost:8080/SvdemoRestful/resources").
-define(PROMO_URL, ?BASE_URL ++ "/promo/").
-define(KEYWORDS_URL, ?BASE_URL "/webPageKeywords"). % also works without ++

keywords_url_for(Url) -&amp;gt; ?KEYWORDS_URL ++ "?url=" ++ Url.
get_keywords(Url) -&amp;gt;
 URL = keywords_url_for(Url),
 { ok, {_Status, _Header, Body} } = http:request(URL),
 Body.
&lt;/pre&gt;
&lt;p&gt;In Erlang, function names and Atoms must start with lower-case letters; variable names must start with upper-case letters or underscore (meaning the variable is not used/read).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-define()&lt;/code&gt; macro in Erlang is similar to &lt;code&gt;#define&lt;/code&gt; in C/C++. In the above example, after defining &lt;code&gt;BASE_URL&lt;/code&gt;, any occurance of &lt;code&gt;?BASE_URL&lt;/code&gt; will be replaced with the string &lt;code&gt;"http://localhost:8080/SvdemoRestful/resources"&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;get_keywords()&lt;/code&gt; function returns the body of the HTTP response from requesting the given &lt;code&gt;Url&lt;/code&gt;. The &lt;code&gt;Body&lt;/code&gt; is either a comma-delimited string, or an empty collection. Executing the above code in Erlang:&lt;/p&gt;
&lt;pre name="code"&gt;
127&amp;gt; c("d:/projects/svdemoErl/svdemoClient.erl").                            
{ok,svdemoClient}
128&amp;gt; svdemoClient:get_keywords("http://localhost/myWebPage.htm").     
"folk songs, pop music, chinese music ,battle, action, comedy "
129&amp;gt; svdemoClient:get_keywords("http://localhost/someOtherPage.htm").
[]
130&amp;gt; 
&lt;/pre&gt;
&lt;p&gt;To consume the second RESTful service, the &lt;code&gt;search_promo()&lt;/code&gt; function is added.&lt;/p&gt;
&lt;pre name="code" class="csharp"&gt;
promo_url_for(Keywords) -&amp;gt; ?PROMO_URL ++ utils:url_encode(Keywords).
search_promo(Keywords) -&amp;gt;
 URL = promo_url_for(Keywords), 
 { ok, {_Status, _Header, Body} } = http:request(URL),
 
 %%% Now that the XML is in the Body variable, let's parse it.
 if
  Body == "&amp;lt;Empty/&amp;gt;" -&amp;gt;
   not_found;
  true -&amp;gt;
   {ParseResult, _Misc} = xmerl_scan:string(Body),
   [ #xmlText{value=Code} ] = xmerl_xpath:string("//Code/text()", ParseResult),
   [ #xmlText{value=Name} ] = xmerl_xpath:string("//Name/text()", ParseResult),
   [ #xmlText{value=Description} ] = xmerl_xpath:string("//Description/text()", ParseResult),
   [ #xmlText{value=Venue} ] = xmerl_xpath:string("//Venue/text()", ParseResult),
   [ #xmlText{value=DateTime} ] = xmerl_xpath:string("//DateTime/text()", ParseResult),
   { Code, Name, Description, Venue, DateTime }
 end.
&lt;/pre&gt;
&lt;p&gt;Erlang/OTP download comes with XML parser and XPath support in the &lt;code&gt;xmerl&lt;/code&gt; application, which is not part of the Erlang standard library (stdlib). To use the XML functions, the header file must be included:&lt;/p&gt;
&lt;pre name="code" class="python"&gt;
-include_lib("xmerl/include/xmerl.hrl").
&lt;/pre&gt;
&lt;p&gt;Note that the keywords contain spaces, which must be URL-encoded before passing to Erlang's &lt;code&gt;http:request()&lt;/code&gt; function. I stole the &lt;code&gt;url_encode()&lt;/code&gt; function from &lt;a href="http://erlyaws.svn.sourceforge.net/viewvc/erlyaws/trunk/yaws/include/yaws.hrl?revision=1248&amp;amp;view=markup"&gt;YAWS &lt;/a&gt;and put it in &lt;code&gt;utils.erl&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;To string the two service consumptions together:&lt;/p&gt;
&lt;pre name="code" class="python"&gt;
search_promo_from_url(Url) -&amp;gt;
 Keywords=get_keywords(Url),
 if
  Keywords == [] -&amp;gt;
   not_found;
  true -&amp;gt;
   search_promo(Keywords)
 end.
&lt;/pre&gt;
&lt;p&gt;Calling the function in Erlang shell:&lt;/p&gt;
&lt;pre name="code"&gt;
126&amp;gt; svdemoClient:search_promo_from_url("http://localhost/MyWebPage.htm")
. 
{"801",
 "Batman The Dark Knight",
 "\n\t\t\tMeet stars in Batman in person - Chritian Bale, Michael Caine.\n\t\t",

 "Star City",
 "2008-7-30 10:00:00"}
&lt;/pre&gt;
&lt;p&gt;The final &lt;code&gt;svdemoClient.erl&lt;/code&gt; file:&lt;/p&gt;
&lt;pre name="code" class="csharp"&gt;
-module(svdemoClient).
-export([get_keywords/1, search_promo/1, search_promo_from_url/1]).
-include_lib("xmerl/include/xmerl.hrl").

-define(BASE_URL, "http://localhost:8080/SvdemoRestful/resources").
-define(PROMO_URL, ?BASE_URL ++ "/promo/").
-define(KEYWORDS_URL, ?BASE_URL "/webPageKeywords"). % also works without ++

keywords_url_for(Url) -&amp;gt; ?KEYWORDS_URL ++ "?url=" ++ Url.
get_keywords(Url) -&amp;gt;
 URL = keywords_url_for(Url),
 { ok, {_Status, _Header, Body} } = http:request(URL),
 Body.

promo_url_for(Keywords) -&amp;gt; ?PROMO_URL ++ utils:url_encode(Keywords).
search_promo(Keywords) -&amp;gt;
 URL = promo_url_for(Keywords), 
 { ok, {_Status, _Header, Body} } = http:request(URL),
 
 %%% Now that the XML is in the Body variable, let's parse it.
 if
  Body == "&amp;lt;Empty/&amp;gt;" -&amp;gt;
   not_found;
  true -&amp;gt;
   {ParseResult, _Misc} = xmerl_scan:string(Body),
   [ #xmlText{value=Code} ] = xmerl_xpath:string("//Code/text()", ParseResult),
   [ #xmlText{value=Name} ] = xmerl_xpath:string("//Name/text()", ParseResult),
   [ #xmlText{value=Description} ] = xmerl_xpath:string("//Description/text()", ParseResult),
   [ #xmlText{value=Venue} ] = xmerl_xpath:string("//Venue/text()", ParseResult),
   [ #xmlText{value=DateTime} ] = xmerl_xpath:string("//DateTime/text()", ParseResult),
   { Code, Name, Description, Venue, DateTime }
 end.
 
search_promo_from_url(Url) -&amp;gt;
 Keywords=get_keywords(Url),
 if
  Keywords == [] -&amp;gt;
   not_found;
  true -&amp;gt;
   search_promo(Keywords)
 end.
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;utils.erl&lt;/code&gt; file (copied from YAWS):&lt;/p&gt;
&lt;pre name="code" class="vb"&gt;
-module(utils).
-export([integer_to_hex/1, url_encode/1]).

integer_to_hex(I) -&amp;gt;
     case catch erlang:integer_to_list(I, 16) of
         {'EXIT', _} -&amp;gt;
             old_integer_to_hex(I);
         Int -&amp;gt;
             Int
     end.
 
 
old_integer_to_hex(I) when I&amp;lt;10 -&amp;gt;
     integer_to_list(I);
old_integer_to_hex(I) when I&amp;lt;16 -&amp;gt;
     [I-10+$A];
old_integer_to_hex(I) when I&amp;gt;=16 -&amp;gt;
     N = trunc(I/16),
     old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16).
 

url_encode([H|T]) -&amp;gt;
     if
         H &amp;gt;= $a, $z &amp;gt;= H -&amp;gt;
             [H|url_encode(T)];
         H &amp;gt;= $A, $Z &amp;gt;= H -&amp;gt;
             [H|url_encode(T)];
         H &amp;gt;= $0, $9 &amp;gt;= H -&amp;gt;
             [H|url_encode(T)];
         H == $_; H == $.; H == $-; H == $/; H == $: -&amp;gt; % FIXME: more..
             [H|url_encode(T)];
         true -&amp;gt;
             case integer_to_hex(H) of
                 [X, Y] -&amp;gt;
                     [$%, X, Y | url_encode(T)];
                 [X] -&amp;gt;
                     [$%, $0, X | url_encode(T)]
             end
     end;
 
url_encode([]) -&amp;gt;
     [].
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;
&lt;span style="font-weight:bold;"&gt;Related Posts:&lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-from-android.html"&gt;Consuming Web Services from Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-by-javame.html"&gt;Consuming Web Services By JavaME&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-wcf-web-service-using-groovy.html"&gt;Consuming WCF Web Service Using Groovy Client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;Consuming WCF Web Service Using Java Client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7328339094749739662?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7328339094749739662/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7328339094749739662' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7328339094749739662'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7328339094749739662'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/10/consuming-restful-service-with-erlang.html' title='Consuming RESTful Service with Erlang'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7415150419422803234</id><published>2008-09-28T11:19:00.005+10:00</published><updated>2008-10-04T10:58:55.982+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Management'/><title type='text'>Hancock: Good job!</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;Last week's experience remindes me of scenes from the movie &lt;a href='http://www.hancock-movie.com/'&gt;Hancock&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The marketing guy Ray tought Hancock how to behave in front of the crowd - here are the dialogues from the scene.&lt;/p&gt;
&lt;quote&gt;&lt;i&gt;
&lt;b&gt;Ray Embrey&lt;/b&gt;: So you've used the door, the building's still intact, people are happy you've arrived, they feel safe now, there's an officer there and he's done a good job, so you might want to tell him he's done a good job.&lt;br/&gt;
&lt;b&gt;Hancock&lt;/b&gt;: What the hell did I have to come for Ray if he's done a good job?
&lt;/i&gt;&lt;/quote&gt;
&lt;p&gt;So later when Hancock when to the crime scene after the police failed to contain the situation, he repeatedly said 'Good job!' to everyone, including the policewoman who was injured and pinned down by enemy fire.&lt;/p&gt;
&lt;quote&gt;&lt;i&gt;
&lt;b&gt;Hancock&lt;/b&gt;: [to pinned-down cop] Good job! Do I have permission to touch your body? &lt;br/&gt;
&lt;b&gt;Female Cop&lt;/b&gt;: Yes! &lt;br/&gt;
&lt;b&gt;Hancock&lt;/b&gt;: It's not sexual. Not that you're not an attractive woman. You're actually a very attractive woman and... &lt;br/&gt;
&lt;b&gt;Female Cop&lt;/b&gt;: [screaming] Get me the hell out of here! &lt;br/&gt;
&lt;/i&gt;&lt;/quote&gt;
&lt;p&gt;Those were the my favorite scenes from the movie. Do the above scenes look familiar in your workplace?&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7415150419422803234?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7415150419422803234/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7415150419422803234' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7415150419422803234'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7415150419422803234'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/hancock-good-job.html' title='Hancock: Good job!'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-22070247979315354</id><published>2008-09-26T11:01:00.003+10:00</published><updated>2008-09-28T10:16:48.338+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Chrome'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Why Not Chrome?</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;I installed and tried &lt;a href='http://www.google.com/chrome/'&gt;Google Chrome&lt;/a&gt; recently and my first impression of Chrome as a browser is quite average. As I &lt;a href='http://romenlaw.blogspot.com/2008/09/why-google-chrome.html'&gt;said before&lt;/a&gt;, there is nothing revolutionary about Chrome at the surface. It's really the backend functionality that Google is betting on. For now, my biggest complaint about Chrome is that it installs in user's home directory rather than a shared directory so that all users can use (maybe there is or will be an option to install it in shared mode and I just don't know it). &lt;/p&gt;
&lt;p&gt;At the moment, each browser has its niche market: IE is pre-installed on all Windows based machines including Windows Mobile; Firefox has a great plug-in framework so that its functionality and usability can be greatly extended beyond just being a browser; Opera has over 100 million installations on mobile phones; Safari is the browser of choice on Mac...&lt;/p&gt;
&lt;p&gt;So far, my favourite browser is Firefox v3 mostly because of the plug-ins. As a blogger, the &lt;a href='http://www.scribefire.com/'&gt;ScribeFire&lt;/a&gt; blogger plug-in for Firefox is a must-have. I use it to do my blogging rather than using the Blogger's own web pages (thanks to &lt;a href='http://code.blogger.com/'&gt;Blogger's APIs&lt;/a&gt;). Another plug-in I use is the &lt;a href='http://adblockplus.org/en/'&gt;Adblock Plus&lt;/a&gt; where you can selectively block advertisement contents by clicking on the 'block' tab that Adblock Plus inserted over them.&lt;/p&gt;
&lt;p&gt;IE still remains the widely used browser and I still use it. I only use it because certain plug-ins (ActiveX) are only available on IE. The most popular plug-in is probably the Microsoft Outlook for IE since most companies these days use Outlook as their email solution.&lt;/p&gt;
&lt;p&gt;Google is also taking this approach by integrating Chrome with other Google services so that Chrome will be the best browser when it comes to Google (and its partner) provided services. Therefore,  I don't see any single browser replacing all others. It will be more of a case of using certain browser for certain use cases. &lt;/p&gt;
Related Posts: &lt;a href='http://romenlaw.blogspot.com/2008/09/why-google-chrome.html'&gt;Why Google Chrome?&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-22070247979315354?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/22070247979315354/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=22070247979315354' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/22070247979315354'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/22070247979315354'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/why-not-chrome_26.html' title='Why Not Chrome?'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5432618626654225876</id><published>2008-09-22T12:28:00.003+10:00</published><updated>2008-09-26T12:17:10.816+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='N95'/><title type='text'>Turn N95 Into A Ping-Pong Bat</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;As I was searching for some games for my N95, I stumbled upon the &lt;a href='http://en.wikipedia.org/wiki/Accelerometer'&gt;Accelerometer &lt;/a&gt;support for N95. If you have seen the iPhone ads on TV, you may have been impressed by the feature of the applications changing their display orientation after the iPhone has been rotated. I must say I was very impressed after playing with my friend's iPhone. Little did I know that my N95 also has the capability to do this. It's just that Nokia did not put any application on it to utilise the accelerometer.&lt;/p&gt;
&lt;p&gt;There are a couple of applications developed by Nokia Research center which take advantage of the accelorometer - Moving Ball and Activity Monitor, both available from the &lt;a href='http://research.nokia.com/projects/activity_monitor'&gt;Nokia Research Center&lt;/a&gt; web site.&lt;/p&gt;
&lt;p&gt;I have downloaded and tried the Moving Ball demo. It was great! As I move/rotate my handset in any direction, the ball moves or bounces following my motion. If I hold the phone like a ping-pong ball bat and swing it, the ball displayed on screen actually bounce up and down against the 'bat'.&lt;/p&gt;
&lt;p&gt;According to these videos, the French developer Samir has made some cool applications using this technology - RotateMe and Nokmote. I particularly like Nokemote where it allows the user to control the application by moving the handset without having to press any buttons/keys - e.g. in the music player, tilt left to rewind, tilt right to fast-forward, bounce it down and up to select song, etc. Unfortunately, the author's web site is down.&lt;/p&gt;
&lt;p&gt;
&lt;/p&gt;&lt;div&gt;&lt;div class='youtube-video'&gt;&lt;object width='420' height='336'&gt;&lt;param name='movie' value='http://www.dailymotion.com/swf/k5B5lOQyogo1kMnt7p&amp;amp;defaultSubtitle=&amp;amp;related=1'&gt; &lt;/param&gt;&lt;param name='allowFullScreen' value='true'&gt; &lt;/param&gt;&lt;param name='allowScriptAccess' value='always'&gt; &lt;/param&gt;&lt;embed src='http://www.dailymotion.com/swf/k5B5lOQyogo1kMnt7p&amp;amp;defaultSubtitle=&amp;amp;related=1' type='application/x-shockwave-flash' allowfullscreen='true' allowscriptaccess='always' width='420' height='336'&gt; &lt;/embed&gt; &lt;/object&gt;&lt;/div&gt;
&lt;b&gt;&lt;a href='http://www.dailymotion.com/video/x3bvyf_rotateme-on-n95'&gt;RotateMe on N95&lt;/a&gt;&lt;/b&gt;
&lt;i&gt;Uploaded by &lt;a href='http://www.dailymotion.com/soueldi'&gt;soueldi&lt;/a&gt;&lt;/i&gt;&lt;/div&gt;
&lt;p/&gt;
&lt;p&gt;
&lt;/p&gt;&lt;div&gt;&lt;div class='youtube-video'&gt;&lt;object width='420' height='336'&gt;&lt;param value='http://dailymotion.alice.it/swf/7IOLQtBA3quTEo6ta' name='movie'&gt; &lt;/param&gt;&lt;param value='true' name='allowFullScreen'&gt; &lt;/param&gt;&lt;param value='always' name='allowScriptAccess'&gt; &lt;/param&gt;&lt;embed allowscriptaccess='always' allowfullscreen='true' type='application/x-shockwave-flash' src='http://dailymotion.alice.it/swf/7IOLQtBA3quTEo6ta' width='420' height='336'&gt; &lt;/embed&gt;&lt;/object&gt;&lt;/div&gt;
&lt;strong&gt;&lt;a href='http://dailymotion.alice.it/video/x3f4o8_nokmote_tech'&gt;Nokmote&lt;/a&gt;&lt;/strong&gt;
&lt;em&gt;Caricato da &lt;a href='http://dailymotion.alice.it/soueldi'&gt;soueldi&lt;/a&gt;&lt;/em&gt;&lt;/div&gt;&lt;p/&gt;
&lt;p&gt;There are others of course: There is a dedicated forum on &lt;a href='http://www.n95users.com/forum/accelerometer/'&gt;N95Users&lt;/a&gt; suggesting all sorts of cool uses with videos to show; &lt;a href='http://www.niime.com/instructions.htm'&gt;NiiMe &lt;/a&gt;is too shaky to be practical; &lt;a href='http://flipsilent.com/tongren/?q=node/29'&gt;FlipSilent &lt;/a&gt; and &lt;a href='http://www.flipsilent.com/tongren/?q=node/31'&gt;ShakeSMS &lt;/a&gt;are pretty good, but I am not sure if they drain the battery; &lt;a href='http://www.landscape-pro.net/'&gt;Landscape Pro&lt;/a&gt;, but it's not free; Alas, all I can do with a great piece of technology is to use it as a ping-pong ball bat!&lt;/p&gt;&lt;/div&gt;
Related Posts: &lt;a href="http://romenlaw.blogspot.com/2008/09/how-to-add-unicode-fonts-to-n95.html"&gt;How To Add Unicode Fonts to N95&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5432618626654225876?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5432618626654225876/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5432618626654225876' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5432618626654225876'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5432618626654225876'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/turn-n95-into-ping-pong-bat.html' title='Turn N95 Into A Ping-Pong Bat'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1139431527201614811</id><published>2008-09-19T21:23:00.001+10:00</published><updated>2008-09-19T21:25:53.458+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GWT'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>The Shortest Self-Printing Code</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;pre class='c#' name='code'&gt;
#!/bin/ksh
cat `whence $0`
&lt;/pre&gt;
&lt;p&gt;Could this be the shortest self-printing code? &lt;img src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif'/&gt;&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1139431527201614811?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1139431527201614811/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1139431527201614811' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1139431527201614811'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1139431527201614811'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/shortest-self-printing-code.html' title='The Shortest Self-Printing Code'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5248368627762802672</id><published>2008-09-13T17:53:00.009+10:00</published><updated>2008-09-15T09:55:44.250+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GWT'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Of Tatami, jMaki and Others...</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;Tatami 1.2 Beta was released a few days ago.&lt;/p&gt;
&lt;p&gt;Both &lt;a href='http://code.google.com/webtoolkit/'&gt;GWT&lt;/a&gt; and &lt;a href='http://dojotoolkit.org/'&gt;Dojo&lt;/a&gt; are Javascript frameworks for creating Rich Internet Applications (RIA). The GWT team focuses on the foundation of the framework and does not bother with making state-of-the-art cool widgets. While Dojo has a mature and good-looking widgets library. Wouldn't it be good if the two can be seamlessly integrated at the API and runtime levels so that one can leverage on the advantages of both frameworks? That's exactly what &lt;a href='http://code.google.com/p/tatami/'&gt;Tatami&lt;/a&gt; offers.&lt;/p&gt;
&lt;p&gt;Tatami is a widget/component library for GWT. What makes it interesting is that it is a GWT wrapper of Dojo components. With Tatami, the Dojo widgets become GWT widgets; the Dojo utilities become GWT helper classes. If you don't have the stomach for editing CSS+XHTML+Javascript directly, but still want to use the Dojo components, then Tatami is good news. The development experience is back to Java and that's it.&lt;/p&gt;
&lt;p&gt;Another web framework which provides Dojo wrapper is the &lt;a href='https://ajax.dev.java.net/'&gt;jMaki framework&lt;/a&gt;. While jMaki has a nifty NetBeans plug-in, the development experience is improved somewhat. However, as a developer, you'd still have to deal with a mixture of JSP, Javascript and Java: the page templates are written in JSP, the glue code is written in Javascript for event handlers (using a pub-sub mechanism), the server side code is written as Java servlets - maybe this hodge-podge is really what they mean by mash-up. &lt;img src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif'/&gt;&lt;/p&gt;
&lt;p&gt;Another wrapper is the GWT-EXT, which is a GWT wrapper for EXT/JS. I have &lt;a href='http://romenlaw.blogspot.com/2008/06/gwt-ria-of-choice.html'&gt;blogged &lt;/a&gt;about the EXT/JS, EXT-GWT and GWT-EXT and the licensing issues revolving EXT/JS previously. Well, the licensing of Dojo is very liberal, without the GPL issues introduced by EXT/JS. Thankfully, Tatami is LGPL.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5248368627762802672?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5248368627762802672/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5248368627762802672' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5248368627762802672'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5248368627762802672'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/of-tatami-jmaki-and-others.html' title='Of Tatami, jMaki and Others...'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-228310937654260599</id><published>2008-09-09T15:53:00.014+10:00</published><updated>2009-08-09T22:03:13.564+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='N95'/><category scheme='http://www.blogger.com/atom/ns#' term='Telecommunications'/><category scheme='http://www.blogger.com/atom/ns#' term='Symbian'/><title type='text'>How To Add Unicode Fonts to Symbian</title><content type='html'>&lt;p&gt;[&lt;strong&gt;Update 2009-02-17&lt;/strong&gt;]: &lt;em&gt;Fontrouter project has been open sourced. Some of the download URLs in this post may have been changed. See &lt;a href="http://romenlaw.blogspot.com/2009/02/fontrouter-open-sourced.html"&gt;here &lt;/a&gt;for more details.&lt;/em&gt;
&lt;/p&gt;
&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;p&gt;If you are like me, who got a &lt;a href='http://my-symbian.com/s60v3/review_n95.php'&gt;Nokia N95 &lt;/a&gt;with only English but wants to view contents in other languages - e.g. Chinese, Japanese, Korean (CJK), Russian, Hindi, etc. then you will be in a bit of strife.&lt;/p&gt;
&lt;p&gt;According to &lt;a href='http://discussions.nokia.com.au/discussions/board/message?board.id=smartphones&amp;amp;message.id=103779&amp;amp;query.id=375392#M103779'&gt;Nokia forum&lt;/a&gt;, there is no way that you can install an extra language package on Nokia. You'd have to go to Nokia Care to have them install it for you, for a fee! Well, from my previous encounters with Nokia Care (here in Sydney), they are bunch of slow, bureaucratic, idiotic waste of space. So that option is a no-no for me.&lt;/p&gt;
&lt;p&gt;Fortunately, the Symbian guru &lt;a href='http://blog.oasisfeng.com'&gt;oasisfeng &lt;/a&gt;has created a wonderful software called FontRouter. The latest incarnation of the software is the &lt;a href='http://fontrouter.oasisfeng.com/forum/viewtopic.php?t=166&amp;amp;sid=556f093d503ed0d145b71ebb5275a8ed'&gt;FontRouter LT&lt;/a&gt;. Because the software is in testing stage, there is no full documentation on how to install and use it. There are pieces of information everywhere in the forum and mostly in Chinese. It took me a day to figure out the end-to-end process of getting it to work on my N95. So I feel that I should record it down and share it. I believe it should work for all S60 v3, S80 and S90 Nokia phones.&lt;/p&gt;
&lt;h2&gt;Step 1 - Downloading the Software&lt;/h2&gt;
There are many versions on the oasisfeng web site, the version that I used was the &lt;a href='http://fontrouter.oasisfeng.com/archives/FontRouter.LT.for.v9.Build20071109.opensigned.sis'&gt;FontRouter LT for Symbian 9 Beta (Build 20071109) Open Signed Online测试版&lt;/a&gt;.&lt;p/&gt;
&lt;p&gt;Also download the TrueType font from the same web site: &lt;a href='http://fontrouter.oasisfeng.com/archives/fzlb_gbk.ttf'&gt;【TrueType】方正隶变 GBK字体&lt;/a&gt;. This contains all the CJK characters(should contain other european characters too). Note that I tried using &lt;a href='http://sc.jcwcn.com/tx/Font.asp-InfoType=11&amp;amp;KeyWord=&amp;amp;page=1.htm'&gt;other Chinese TrueType fonts &lt;/a&gt;and my handset failed to start. So experiment with other .ttf files only after you have successfully installed and tested with this one. I am currently using the 方正准圆(FZY3JW.TTF) which is a Chinese true type font and its English characters are very close to the original font on my Nokia N95.&lt;/p&gt;
&lt;p&gt;Notice that all Nokia phones belong to a certain profile (to make it easy for software developers to select and test the target platform/handsets). The N95 is an S60 3rd edition phone and runs on Symbian version 9. You can find this out from the phone: Tools -&amp;gt; Utilities -&amp;gt; About. Also notice that the version of FontRouter that I used was "&lt;em&gt;Open Signed Online&lt;/em&gt;". What does this mean? Read on...&lt;/p&gt;
&lt;h2&gt;Step 2 - Signing the Software&lt;/h2&gt;
&lt;p&gt;Symbian is pretty rigorous when it comes to security. Software needs to be &lt;a href='http://en.wikipedia.org/wiki/Digital_signature'&gt;digitally signed&lt;/a&gt; before installing onto the phone. You can try installing the &lt;code&gt;.sis&lt;/code&gt; file just as it is and your phone will reject it and complain that the digital certificate/signature is invalid or something of that nature. To sign the file you just downloaded, you will have to go to &lt;a href='https://www.symbiansigned.com/app/page'&gt;www.symbiansigned.com&lt;/a&gt;. On its front page, there is a URL for &lt;a href='https://www.symbiansigned.com/app/page/public/openSignedOnline.do'&gt;Open Signed Online&lt;/a&gt; (Beta). Click this URL and fill in the form on the following page. Note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;IMEI number &lt;/strong&gt;uniquely identifies your handset (e.g. if your phone is stolen, you can report it to the carrier and they can bar the IMEI from the network so that the thief cannot use it). You can find it out by 'dialing' *#06# 
&lt;/li&gt;&lt;li&gt;Email - make sure you use a valid email address because you will need to retrieve email from Symbian to carry out the next steps.
&lt;/li&gt;&lt;li&gt;The &lt;strong&gt;Application&lt;/strong&gt; is the &lt;code&gt;.sis&lt;/code&gt; file you just downloaded from FontRuter.
&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Upon successful submission of the form, Symbian will send you an email with a URL to confirm the submission. Click it.&lt;/p&gt;
&lt;p&gt;Then Symbian will send you another email with a URL to download the signed version of the .sis file. Just click the URL and save the file.&lt;/p&gt;
&lt;h2&gt;Step 3 - Installation&lt;/h2&gt;
&lt;p&gt;Now you can install the signed version of the software that you just downloaded from Symbian using &lt;a href='http://www.nokia.com.au/A4630619'&gt;PC Suite&lt;/a&gt;. &lt;strong&gt;Make sure you install it on the Memory Card, NOT on the Phone Memory &lt;/strong&gt;(otherwise, if it hangs your handset, you will not be able to bring your phone back to life). During installation, it will warn you that your phone is incompatible with the software, just ignore it and continue.&lt;/p&gt;
&lt;p&gt;Then copy the &lt;code&gt;fzlb_gbk.ttf&lt;/code&gt; file downloaded in Step 1 into the Memory Card's &lt;code&gt;\data\Fonts&lt;/code&gt; directory. You will notice that the file &lt;code&gt;FontRouter.ini&lt;/code&gt; has also been created in this directory by the software installation process.&lt;/p&gt;
&lt;p&gt;Now it is time to modify the &lt;code&gt;FontRouter.ini&lt;/code&gt; file. You can use the Windows Nodepad to edit a copy of the file and then copy it onto the handset using PC Suite. Modify the two lines of the .ini file as following:&lt;/p&gt;
&lt;pre&gt;
FixFontMetrics=0
FixCharMetrics=0
&lt;/pre&gt;
changed the values from 0 to 1 like this:
&lt;pre&gt;
FixFontMetrics=1
FixCharMetrics=1
&lt;/pre&gt;
and leave everything else the same. If you don't make the changes, the fonts will be curtailed either from the top or bottom.
&lt;p&gt;That's it. Phew! Now it's time to test it - bounce the phone (i.e. switch it off then on). &lt;/p&gt;&lt;h1&gt;Euphoria!&lt;/h1&gt;
&lt;p/&gt;
&lt;p&gt;
There are a few must-read guides that I highly recommend:
&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;a href='http://fontrouter.oasisfeng.com/forum/viewtopic.php?t=2'&gt;Test Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href='http://fontrouter.oasisfeng.com/forum/viewtopic.php?t=420'&gt;Font Location&lt;/a&gt;&lt;/li&gt;
&lt;p/&gt;
&lt;/ol&gt;&lt;/div&gt;
Related posts: &lt;a href="http://romenlaw.blogspot.com/2008/09/turn-n95-into-ping-pong-bat.html"&gt;Turn N95 Into Ping-Pong Bat&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-228310937654260599?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/228310937654260599/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=228310937654260599' title='36 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/228310937654260599'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/228310937654260599'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/how-to-add-unicode-fonts-to-n95.html' title='How To Add Unicode Fonts to Symbian'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>36</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8915105978764795096</id><published>2008-09-05T23:22:00.010+10:00</published><updated>2008-09-28T10:15:42.498+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GWT'/><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><category scheme='http://www.blogger.com/atom/ns#' term='Chrome'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Why Google Chrome?</title><content type='html'>&lt;div xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;img border='0' style='margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;' src='http://dtt0ua.blu.livefilestore.com/y1pVtXd5RBW4LVTeCyBspv1eJ92erQabUnC5udFcBWg4Sgw-1-n68xAXCcgKklh_CLKFparD1FFScQ/chrome.jpeg' alt=''/&gt;
&lt;p&gt;The release of &lt;a href='http://www.google.com/chrome/'&gt;Google Chrome beta &lt;/a&gt;is indead exciting news. Chrome is going to be bigger than iPhone! What makes it exciting is not the GUI - in fact, it looks and behaves pretty much just like any other browser. It is what's under the hood that make people (OK, developers) pumped about the new browser.&lt;/p&gt;
&lt;p&gt;Google made it very clear on &lt;a href='http://www.google.com/chrome/intl/en/why.html?hl=en'&gt;why they built a browser&lt;/a&gt;: "&lt;em&gt;What we really needed was not just a browser, but also a modern platform for web pages and applications, and that's what we set out to build&lt;/em&gt;." So it is clear that Google intends to make Chrome to host and run next generation rich internet applications (RIA). Google's RIA platform is the &lt;a href='http://code.google.com/webtoolkit/'&gt;Google Web Toolkit (GWT)&lt;/a&gt;, which just had version &lt;a href='http://googlewebtoolkit.blogspot.com/2008/08/gwt-15-now-available.html'&gt;1.5 released &lt;/a&gt;a few days ago (Developer's Guide &lt;a href='http://code.google.com/docreader/#p=google-web-toolkit-doc-1-5'&gt;here&lt;/a&gt;). JavaFX folks are also excited about Chrome as shown in this &lt;a href='http://java.dzone.com/articles/javafx-applets-meet-google-chr'&gt;article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Google is a big proponent of Javascript. GWT 'compiles' Java code into Javascript and uses Javascript in runtime. A sophisticated GWT application can result in a large Javascript file, which poses a performance problem for the initial download of the application (a similar problem faced by Java Applets, which Java 6 Update 10 solves by allowing downloading relevant JARs only, without having to download the whole JRE libraries). &lt;a href='http://code.google.com/apis/gears/'&gt;Google Gears &lt;/a&gt;is another component that Google pushes to increase performance of Javascript applications. Yet, users have to download and install it separately if they want to take advantage of it. Therefore, it is natural for Google to come up with a browser as a unified platform for great support of Javascript. From Chrome web site, Google stated: "&lt;em&gt;We also built V8, a more powerful JavaScript engine, to power the next generation of web applications that aren't even possible in today's browsers&lt;/em&gt;." Here we can see a glimpse of what to expect from Google on the Chrome, Gears and GWT fronts. GWT applications will run faster on Chrome than any other browser, thanks to Javascript optimisation on Chrome.&lt;/p&gt;
&lt;p&gt;No wonder people are speculating that Microsoft is contemplating on &lt;a href='http://techliberation.com/2008/09/02/google-chrome-the-coming-antitrust-suits/'&gt;suing Google for antitrust&lt;/a&gt;!&lt;img border='0' class='inlineimg' title='Wink' alt='' src='http://www.sunniforum.com/forum/images/smilies/icon_wink.gif'/&gt;&lt;/p&gt;
Related posts: &lt;a href='http://romenlaw.blogspot.com/2008/09/why-not-chrome_26.html'&gt;Why Not Chrome?&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8915105978764795096?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8915105978764795096/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8915105978764795096' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8915105978764795096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8915105978764795096'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/why-google-chrome.html' title='Why Google Chrome?'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-403557814778705920</id><published>2008-09-03T23:54:00.006+10:00</published><updated>2008-09-04T13:06:10.827+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Management'/><category scheme='http://www.blogger.com/atom/ns#' term='Entity Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Design'/><category scheme='http://www.blogger.com/atom/ns#' term='ORM'/><title type='text'>The Sorry State of ADO.NET Entity Framework 1.0</title><content type='html'>&lt;p&gt;A few months ago I test-drove the ADO.NET Entity Framework 1.0 Beta 3. While I enjoyed certain aspects of EF, such as LINQ and graphical modelling, the overall experience of using EF hasn't been that great. In fact, my very &lt;a href="http://romenlaw.blogspot.com/2008/06/entity-framework-beta-3.html"&gt;first blog on this site &lt;/a&gt;was dedicated to EF bashing. My major complaint about EF was its intrusive approach to ORM - instead of using PONO (like in Hibernate), it generates bloated relational database-centric data entity models. This is against the domain driven design method. &lt;a href="http://romenlaw.blogspot.com/2008/06/entity-framework-bug.html"&gt;Another problem &lt;/a&gt;I encountered was its shoddy implementation of lazy loading (or lack of it, as a matter of fact).&lt;/p&gt;
&lt;p&gt;
I then realised that I was not alone in feeling disenchanted by EF. In the same month of my blog, several hundred people have signed up on the &lt;a href="http://efvote.wufoo.com/forms/ado-net-entity-framework-vote-of-no-confidence/"&gt;ADO.NET Entity Framework Vote of No-Confidence &lt;/a&gt;open letter, many of whom are from Microsoft's Most Valued Partners (MVP). In last month's issue of Visual Studio Magazine, the article &lt;a href="http://visualstudiomagazine.com/columns/article.aspx?editorialsid=2724"&gt;A Vote for Transparency&lt;/a&gt; gave a blow-by-blow account of events on the controversy surrounding EF, including the aformentioned open letter. So Microsoft decided to make the EF version 2 design exercise a more transparent process. This no doubt is a step forward. However, it is too little too late for EF v1. Looks like EF v1 will not receive any significant improvements or address any of the communities concerns in the open letter. In fact, I doubt that any EF design improvements have been made into &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyId=AB99342F-5D1A-413D-8319-81DA479AB0D7&amp;displaylang=en"&gt;.NET Framework 3.5 SP1 &lt;/a&gt;(released in August 2008) since EF 1.0 Beta 3 (released in December 2007).&lt;/p&gt;
&lt;p&gt;
On a side note: although some people whine about &lt;a href="http://jcp.org/en/home/index"&gt;JCP&lt;/a&gt; program being slow (e.g. the &lt;a href="http://romenlaw.blogspot.com/2008/07/xenophobia.html"&gt;Java closure debate&lt;/a&gt;) and sometimes committee-driven, comparing to the traditional closed-door design pattern like EF v1, JCP is a great leap forward and the result speaks for itself. The JCP specifications are more readily accepted and adopted by the community than a proprietary one like EF.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-403557814778705920?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/403557814778705920/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=403557814778705920' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/403557814778705920'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/403557814778705920'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/09/sorry-state-of-adonet-entity-framework.html' title='The Sorry State of ADO.NET Entity Framework 1.0'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4896868836695118100</id><published>2008-08-25T09:52:00.014+10:00</published><updated>2009-02-15T14:55:25.131+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='RIA'/><title type='text'>The Sorry State of JavaFX</title><content type='html'>[&lt;strong&gt;Update&lt;/strong&gt;: &lt;em&gt;since JavaFX 1.0 has come out, this article is out-dated. See &lt;a href="http://romenlaw.blogspot.com/2008/12/sorry-state-of-javafx-10.html"&gt;The Sorry State of JavaFX 1.0&lt;/a&gt; instead&lt;/em&gt;.]
&lt;p&gt;It's been over a year since F3 has been rebadged JavaFX in May 2007. I had enjoyed programming in JavaFX script and it really is easy to learn and enables rapid application development. &lt;/p&gt;
&lt;p&gt;JavaFX is fine for its original &lt;a href="http://blogs.sun.com/chrisoliver/category/JavaFX"&gt;goal of delivering a 'media' stack for the Java platform&lt;/a&gt;. However, as JavaFX is more and more being pitched to compete with Silverlight and Flash to be Sun's answer to Rich Internet Application (RIA) platform, it is falling short.&lt;/p&gt;
&lt;p&gt;A major requirement of a RIA platform is to be able to run the application seamlessly in a web browser - i.e. using the browser as a canvas, not just using the browser to download the fat client. JavaFX does not do that. Both Silverlight and Flash's home pages promote their own technologies by using them. But what does &lt;a href="http://javafx.com/"&gt;javafx.com &lt;/a&gt;use? Javascript! So far almost none of the JavaFX demos show an application running in browser. For the few that do show, such as the &lt;a href="http://jfx.wikia.com/wiki/Applet_Example"&gt;Applet Example&lt;/a&gt;, when I tried it using &lt;a href="http://java.sun.com/javafx/reference/releasenotes/javafx-sdk-release-notes.html"&gt;javafx-sdk1.0pre1&lt;/a&gt;, all I got was a blank applet canvas. (I did manage to run a JavaFX application as an Applet from HTML via trial and error though).&lt;/p&gt;
&lt;p&gt;Also, no support for being inlineable with HTML will decrease its flexibility as a RIA. Tey Chui gave &lt;a href="http://www.redmountainsw.com/wordpress/archives/why-javafx-should-be-inlineable-with-html"&gt;a comprehensive argument &lt;/a&gt;on this issue.&lt;/p&gt;
&lt;p&gt;Sun has been promising a browser javafx plugin for a while now. I do hope they can deliver it together with the proper 1.0 release. Talking about the releases, I was really surprised by the drastic changes made in the current 1.0pre1 release. It has pervasively changed the JavaFX script syntax so that your previous JavaFX code in the last 2 years simply will not compile anymore. Furthermore, the bundled JARs have also been totally changed - the &lt;code&gt;javafx.ui.*&lt;/code&gt; simply disappeared! This suggests a &lt;a href="http://java.sun.com/developer/community/askxprt/sessions/2008/jl0818.jsp"&gt;deeper underlying architectural change&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Well, making big changes may not be a bad thing, especially considering JavaFX' beta status. However, the documentation available in supporting the developer community to cope with the change is totally inadequate. Sun does have &lt;a href="http://java.sun.com/javafx/reference/"&gt;JavaFX Reference &lt;/a&gt;documents. However, the current state of these documents are nothing near to be complete or even adequate. For example, the &lt;a href="http://openjfx.dev.java.net/migration.html"&gt;Migration Guide &lt;/a&gt; does not tell you what had happed to '&lt;code&gt;trigger on new&lt;/code&gt;' or nullable datatypes (e.g. &lt;code&gt;int?&lt;/code&gt;) or recommendations for replacing &lt;code&gt;javafx.ui.*&lt;/code&gt; with the scene graph libraries... &lt;/p&gt;
&lt;P&gt;For now, I will put my JavaFX exercises on halt and wait for the final v1.0 release.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4896868836695118100?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4896868836695118100/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4896868836695118100' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4896868836695118100'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4896868836695118100'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/sorry-state-of-javafx.html' title='The Sorry State of JavaFX'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-1218668990518912047</id><published>2008-08-22T13:07:00.021+10:00</published><updated>2008-10-09T23:07:05.186+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web Service'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='JavaME'/><category scheme='http://www.blogger.com/atom/ns#' term='Android'/><title type='text'>Consuming Web Services from Android</title><content type='html'>&lt;p&gt;Earlier this week, &lt;a href="http://www.pcmag.com/article2/0,2817,2328356,00.asp"&gt;Google released Android 0.9 SDK Beta&lt;/a&gt;. As usual, I couldn't wait to try it out.&lt;/p&gt;
&lt;p&gt;Unlike JavaME (especially MIDP 1.0), Android is targetting mobile devices with more grunt - it's bundled with many open source libraries, such as Apache Harmony (open source Java SE), &lt;a href="http://www.icu-project.org/"&gt;Unicode library&lt;/a&gt;, JUnit, Apache Commons, ASN.1 libarary and more from &lt;a href="http://www.bouncycastle.org/java.html"&gt;Bouncy Castle&lt;/a&gt;, &lt;a href="http://kxml.sourceforge.net/about.shtml"&gt;kXML&lt;/a&gt;, etc. The android.jar file is 11MB!&lt;/p&gt;
&lt;p&gt;It is time to write a web service consumer on Android, to consume my &lt;a href="http://romenlaw.blogspot.com/2008/07/wcf-is-so-easy.html"&gt;WCF web service&lt;/a&gt; as well as &lt;a href="http://romenlaw.blogspot.com/2008/08/restful-web-service.html"&gt;RESTful service &lt;/a&gt;that I developed for a demo.&lt;/p&gt;
&lt;p&gt;The Android does not contain any tools to help building SOAP based web service clients. Google is a proponent of REST services. It is no surprise that the SDK is not bundled with any SOAP-related tools. An alternative is to add &lt;a href="http://ksoap2.sourceforge.net/"&gt;kSOAP 2&lt;/a&gt; to my test project. But I quickly dismissed the idea as my web service built in WCF is not &lt;a href="http://jcp.org/aboutJava/communityprocess/final/jsr172/index.html"&gt;JSR-172 &lt;/a&gt;compliant.&lt;/p&gt;
&lt;p&gt;Like my &lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-by-javame.html"&gt;exercise in JavaME&lt;/a&gt;, I decided to call the &lt;a href="http://romenlaw.blogspot.com/2008/08/restful-web-service.html"&gt;RESTful version of the same service built in NetBeans 6.1&lt;/a&gt; instead.&lt;p&gt;
&lt;p&gt;I created a new Android project - &lt;code&gt;SvdemoAndroid&lt;/code&gt;, using Eclipse 3.4 with the &lt;a href="http://code.google.com/android/intro/develop-and-debug.html#developingwitheclipse"&gt;Android plugin &lt;/a&gt;installed. Because my Android Activity will be accessing the internet for the HTTP connection (although it's hosted on my machine), I had to add the line &amp;lt;uses-permission android:name="android.permission.INTERNET" /&amp;gt; to my &lt;code&gt;AndroidManifest.xml&lt;/code&gt; file. Now the file looks something like:&lt;/p&gt;
&lt;pre name="code" class="xml"&gt;
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="svdemo.android"&gt;
    &amp;lt;uses-permission android:name="android.permission.INTERNET" /&amp;gt; 
    &lt;application android:icon="@drawable/icon" android:label="@string/app_name"&gt;
        &lt;activity android:name=".SvdemoAndroid" android:label="@string/app_name"&gt;
            &lt;intent-filter&gt;
                &amp;lt;action android:name="android.intent.action.MAIN" /&amp;gt;
                &amp;lt;category android:name="android.intent.category.LAUNCHER" /&amp;gt;
            &lt;/intent-filter&gt;
        &lt;/activity&gt;
    &lt;/application&gt;
&lt;/manifest&gt; 
&lt;/pre&gt;
&lt;p&gt;Android is bundled with &lt;a href="http://hc.apache.org/httpcomponents-client/index.html"&gt;Apache HttpClient 4&lt;/a&gt;. So invoking a REST service is simply a matter of openning up a HTTP link using the HttpClient and process the response. The following two methods show the consumption of two different RESTful services. The first method &lt;code&gt;getKeywords()&lt;/code&gt; calls a service returning a plain text on HTTP.&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
        static String SERVER_HOST="192.168.2.100";
 static int SERVER_PORT = 8080;
 static String URL1 = "/SvdemoRestful/resources/mySpace?url=http://wpickup02/MySpace_Com_John.htm";
 static String URL2 = "/SvdemoRestful/resources/promo";
 /**
     * Call the RESTful service to get a list of keywords from the web page.
     * @param target - the target HTTP host for the RESTful service.
     * @return - comma delimited keywords. May contain spaces. If no keywords found, return null.
     */
    private String getKeywords(HttpHost target) {
        String keywords=null;
     HttpEntity entity = null;
     HttpClient client = new DefaultHttpClient();
     HttpGet get = new HttpGet(URL1);
     try {
   HttpResponse response=client.execute(target, get);
   entity = response.getEntity();
   keywords = EntityUtils.toString(entity);
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   if (entity!=null)
    try {
     entity.consumeContent();
    } catch (IOException e) {}
  }
  return keywords;
    }
&lt;/pre&gt;
&lt;p&gt;The second method calls another service (on &lt;code&gt;URL2&lt;/code&gt;). This service returns a XML string containing the details of a Promotion. Unlike JavaME, Android SDK is bundled with DOM parser. Since my XML string is pretty short I decided to use DOM instead of kXML's pull parser. Note that &lt;code&gt;PromoInfo&lt;/code&gt; is just a data object holding all the promotion information in its fields.
&lt;pre name="code" class="java"&gt;
/**
     * Call the REST service to retrieve the first matching promotion based
     * on the give keywords. If none found, return null.
     * @param target - the target HTTP host for the REST service.
     * @param keywords - comma delimited keywords. May contain spaces.
     * @return - PromoInfo that matches the keywords. If error or no match, return null.
     */
    private PromoInfo searchPromo(HttpHost target, String keywords) {
     if(keywords==null)
      return null;
     
     PromoInfo promo=null;
     Document doc = null;
     HttpClient client = new DefaultHttpClient();
     HttpGet get = new HttpGet(URL2+"/"+keywords.replaceAll(" ", "%20"));
     try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
   DocumentBuilder builder = factory.newDocumentBuilder();
   HttpEntity entity = client.execute(target, get).getEntity();
   
   doc = builder.parse(entity.getContent());
   NodeList promotions = doc.getElementsByTagName("Promotion");
   
   if(promotions!=null) {
    Node promoNode = promotions.item(0);
    
    if (promoNode!=null) {
     promo=new PromoInfo();
     NodeList nodeList = promoNode.getChildNodes();
     int len = nodeList.getLength();
     for(int i=0; i&amp;lt;len; i++) {
      Node node = nodeList.item(i);
      String value = this.getNodeValue(node);
      if("Name".equals(node.getNodeName())) {
       promo.setName(value);
      } else if("Description".equals(node.getNodeName())) {
       promo.setDescription(value);
      } else if("Venue".equals(node.getNodeName())) {
       promo.setVenue(value);
      } else if("Name".equals(node.getNodeName())) {
       promo.setName(value);
      } else if("DateTime".equals(node.getNodeName())) {
       promo.setDateTime(value);
      }
     }
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
   promo=null;
  } finally {
   
  }
  return promo;
    }
    private String getNodeValue(Node node) {
     NodeList children = node.getChildNodes();
     if(children.getLength()&amp;gt;0) {
      return children.item(0).getNodeValue();
     } else
      return null;
    }
&lt;/pre&gt;
&lt;p&gt;Now that I have invoked the web services and got the data back to my Android phone, it is time to display them on screen. Android adopts a similar approach to &lt;a href="http://en.wikipedia.org/wiki/Windows_Presentation_Foundation"&gt;Microsoft WPF&lt;/a&gt;, where you can define the view layout and styles in a XML file, so that the Java code is freed up from screen rendering code and can focus more on the business logic.&lt;/p&gt;
&lt;p&gt;I created a &lt;code&gt;style.xml&lt;/code&gt; file under the {&lt;em&gt;project&lt;/em&gt;}&lt;code&gt;/res/values&lt;/code&gt; directory to define the styles for my screen widgets.&lt;/p&gt;
&lt;pre name="code" class="xml"&gt;
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;resources&gt;
    &amp;lt;style name="LabelText"&amp;gt;
        &lt;item name="android:textSize"&gt;18sp&lt;/item&gt;
        &lt;item name="android:textColor"&gt;#fff&lt;/item&gt;
        &lt;item name="android:layout_width"&gt;fill_parent&lt;/item&gt; 
        &lt;item name="android:layout_height"&gt;wrap_content&lt;/item&gt; 
        &lt;item name="android:paddingTop"&gt;5px&lt;/item&gt;
    &amp;lt;/style&amp;gt;
    &amp;lt;style name="ContentText"&amp;gt;
        &lt;item name="android:textSize"&gt;14sp&lt;/item&gt;
        &lt;item name="android:textColor"&gt;#000&lt;/item&gt;
        &lt;item name="android:background"&gt;#e81&lt;/item&gt;
        &lt;item name="android:cursorVisible"&gt;true&lt;/item&gt;
        &lt;item name="android:text"&gt;&lt;/item&gt;
        &lt;item name="android:layout_width"&gt;fill_parent&lt;/item&gt; 
        &lt;item name="android:layout_height"&gt;wrap_content&lt;/item&gt; 
    &amp;lt;/style&amp;gt;
&lt;/resources&gt;
&lt;/pre&gt;
&lt;p&gt;Then my view layout XML can use the styles. Here is the {&lt;em&gt;project&lt;/em&gt;}&lt;code&gt;/res/layout/main.xml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre name="code" class="xml"&gt;
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    &gt;
    &amp;lt;TextView style="@style/LabelText" android:text="Name:"/&amp;gt;
    &amp;lt;TextView style="@style/ContentText" android:id="@+id/tvName" /&amp;gt;
    &amp;lt;TextView style="@style/LabelText" android:text="Description:"/&amp;gt;
    &amp;lt;TextView style="@style/ContentText" android:id="@+id/tvDescription" /&amp;gt;
    &amp;lt;TextView style="@style/LabelText" android:text="Venue:"/&amp;gt;
    &amp;lt;TextView style="@style/ContentText" android:id="@+id/tvVenue" /&amp;gt;
    &amp;lt;TextView style="@style/LabelText" android:text="Date:"/&amp;gt;
    &amp;lt;TextView style="@style/ContentText" android:id="@+id/tvDate" /&amp;gt;
&lt;/LinearLayout&gt;
&lt;/pre&gt;
&lt;p&gt;Note that by adding the &lt;code&gt;android:text="@+id/tvName"&lt;/code&gt;, the Android Eclipse Plugin will automatically regenerate the &lt;code&gt;R.java&lt;/code&gt; file to add fields into the &lt;code&gt;id&lt;/code&gt; class, so that you can reference the widget from Java code using &lt;code&gt;findViewById(R.id.tvName)&lt;/code&gt;. Here is the code for the &lt;code&gt;onCreate()&lt;/code&gt; method:&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        HttpHost target = new HttpHost(SERVER_HOST, SERVER_PORT, "http");
        String keywords = getKeywords(target);
        
        setContentView(R.layout.main);
        if(keywords!=null) {
         PromoInfo promo = searchPromo(target, keywords);
         if(promo!=null) {
          ((TextView)this.findViewById(R.id.tvName)).setText(promo.getName());
          ((TextView)this.findViewById(R.id.tvDescription)).setText(promo.getDescription());
          ((TextView)this.findViewById(R.id.tvDate)).setText(promo.getDateTime());
          ((TextView)this.findViewById(R.id.tvVenue)).setText(promo.getVenue());
         }
        }
    }
&lt;/pre&gt;
&lt;p&gt;Running the application:&lt;/p&gt;
&lt;img alt='Android screenshot' src='http://dtt0ua.blu.livefilestore.com/y1pUvew9Q77dW5_1L_pHyhLOIN13M6ToRC3rizg9puX9vJquSvtRWSxlOf4zVBsv75JXYNYnzDchEw/android.png'/&gt;
&lt;p&gt;
&lt;span style="font-weight:bold;"&gt;Related Posts:&lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/10/consuming-restful-service-with-erlang.html"&gt;Consuming RESTful Service with Erlang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-by-javame.html"&gt;Consuming Web Services By JavaME&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-wcf-web-service-using-groovy.html"&gt;Consuming WCF Web Service Using Groovy Client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;Consuming WCF Web Service Using Java Client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-1218668990518912047?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/1218668990518912047/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=1218668990518912047' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1218668990518912047'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/1218668990518912047'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/consuming-web-services-from-android.html' title='Consuming Web Services from Android'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-2296919955323679373</id><published>2008-08-19T22:17:00.024+10:00</published><updated>2008-10-09T23:07:39.147+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Web Service'/><category scheme='http://www.blogger.com/atom/ns#' term='SOA'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='JavaME'/><title type='text'>Consuming Web Services By JavaME</title><content type='html'>&lt;p&gt;The &lt;a href="http://en.wikipedia.org/wiki/Javame"&gt;J2ME &lt;/a&gt;was designed for very resource-constrained hardware. As the devices get more and more powerful (N95, iPhone, HTC Android phones), the JavaME's Mobile Information Device Profiles (MIDP) desperately need an upgrade. Before &lt;a href="http://www.jcp.org/en/jsr/detail?id=271"&gt;JSR-271 (MIDP 3.0)&lt;/a&gt; is finalised Sun has recognised the poor UI capability of JavaME and released the &lt;a href="https://lwuit.dev.java.net/"&gt;Light Weight UI Toolkit (LWUIT)&lt;/a&gt; to stay competitive. Another good 3rd-party framework is &lt;a href="http://j2mepolish.org/cms/"&gt;J2ME Polish&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here I attempt to call the same web services that I developed as part of a demo using JavaME and then display the results using LWUIT.&lt;/p&gt;
&lt;p&gt;I first tried to consume the &lt;a href="http://romenlaw.blogspot.com/2008/07/wcf-is-so-easy.html"&gt;web services developed using WCF&lt;/a&gt;. The Sun's Wireless Toolkit (&lt;a href="http://java.sun.com/products/sjwtoolkit/download.html"&gt;WTK2.5.2&lt;/a&gt;) comes with a utility to generate SOAP client proxy from WSDL. (An alternative is to use &lt;a href="http://ksoap2.sourceforge.net/"&gt;KSOAP2&lt;/a&gt;). However, JavaME only supports a subset of JAXP and JAX-RPC as defined in &lt;a href="http://jcp.org/aboutJava/communityprocess/final/jsr172/index.html"&gt;JSR-172&lt;/a&gt;. My web service data contract contains a DateTime field, which violates JSR-172. Also it is mapped to &lt;code&gt;java.util.Calendar&lt;/code&gt;, which is not supported by JavaME. So I could not call the SOAP web service as it is from JavaME. I guess I will have to wait for MIDP 3.0 or even later. In any case, full SOAP web services are too much of a heavy weight for mobile devices. It is better to use RESTful services.&lt;/p&gt;
&lt;p&gt;Fortunately, I have also developed a &lt;a href="http://romenlaw.blogspot.com/2008/08/restful-web-service.html"&gt;RESTful version of the services&lt;/a&gt; using NetBeans 6.1 under the project &lt;strong&gt;SvdemoRestful&lt;/strong&gt;. So I created a MIDP 2.0 project named &lt;strong&gt;SvdemoWUIT&lt;/strong&gt; in NetBeans and included the LWUIT JAR in the project.&lt;/p&gt;
&lt;p&gt;There are two quick and easy ways to consume RESTful services:&lt;/p&gt;&lt;a href="http://3.bp.blogspot.com/_ILzIJXnrA40/SKrFs1MfjmI/AAAAAAAAABU/HExBablwK98/s1600-h/snaggit.PNG"&gt;&lt;img id="BLOGGER_PHOTO_ID_5236214890756017762" style="FLOAT: right; MARGIN: 0px 0px 10px 10px; CURSOR: hand" alt="" src="http://3.bp.blogspot.com/_ILzIJXnrA40/SKrFs1MfjmI/AAAAAAAAABU/HExBablwK98/s400/snaggit.PNG" border="0" /&gt;&lt;/a&gt;
&lt;p&gt;The first way is to use one of NetBeans MIDP wizards to create "&lt;em&gt;Mobile Client to Web Application&lt;/em&gt;". This wizard will take a Web Application (or a Web App project in NB) to discover the RESTful services and generates a pair of servlet (in the web app project) and mobile client (in the MIDP project) code to wrap the service. The screenshot shows the generated files highlighted in red. Using the generated mobile client in the MIDlet:&lt;/p&gt;
&lt;pre class="java" name="code"&gt;
// create an instance of the mobile client proxy
WebToMobileClient client=new WebToMobileClient();

// consume the web service via the client
String keywords = client.getText("http://localhost/MySpace_Com_John.htm");
System.out.println("keywords:"+keywords);
String promoXml = client.getXml(keywords);
System.out.println("promo:"+promoXml);           
&lt;/pre&gt;
The corresponding stdout shows:
&lt;pre name="code" class="xml"&gt;
keywords:folk songs, pop music, chinese music ,battle, action, comedy
promo:&lt;promotion&gt;&lt;code&gt;801&lt;/code&gt;&lt;name&gt;Batman The Dark Knight&lt;/name&gt;&lt;description&gt;
                        Meet stars in Batman in person - Chritian Bale, Michael Caine.
                &lt;/description&gt;&lt;venue&gt;Star City&lt;/venue&gt;&lt;datetime&gt;2008-7-30 10:00:00&lt;/datetime&gt;&lt;tags&gt;Batman, action&lt;/tags&gt;&lt;/promotion&gt;
&lt;/pre&gt;
&lt;p&gt;There seemed to be a bug in the wizard: it only generated methods from one web service resource of the SvdemoRestful project although I specified both resources. Therefore, I had to modify the &lt;code&gt;Utility.java&lt;/code&gt; and &lt;code&gt;WebToMobileClient.java&lt;/code&gt; by hand to add the missing methods.&lt;/p&gt;
&lt;p&gt;The second way is to use the &lt;code&gt;javax.microedition.io.HttpConnection&lt;/code&gt; and deal with the low level stuff myself. I needed to parse the Promotion XML and to populate the &lt;code&gt;PromoInfo&lt;/code&gt; object. JavaME only offers SAX for XML parsing, so I had to get the &lt;code&gt;InputStream&lt;/code&gt; from the &lt;code&gt;HttpConnection&lt;/code&gt; and pass it to the &lt;code&gt;SAXParser&lt;/code&gt;. SAX uses callback mechanism. Therefore, a &lt;code&gt;XmlHandler&lt;/code&gt; class had to be defined. &lt;/p&gt;
&lt;p&gt;The MIDlet code snippet:&lt;/p&gt;
&lt;pre class="java" name="code"&gt;
public class SvdemoWUITMidlet extends MIDlet implements ActionListener {
    static String promoUrl="http://localhost:8080/SvdemoRestful/resources/promo";
    // data object to hold the XML contents in its corresponding fields
    PromoInfo promo=null;
    public PromoInfo getPromo() {
        return this.promo;
    }
    public void setPromo(PromoInfo promo) {
        this.promo=promo;
    }
    public void startApp() {
        Display.init(this);
        HttpConnection hc=null;

        WebToMobileClient client=new WebToMobileClient();
        try {
            String keywords = client.getText("http://localhost/MySpace_Com_John.htm");
            System.out.println("keywords:"+keywords);
            String promoXml = client.getXml(keywords);
            System.out.println("promo:"+promoXml);
           
            hc=(HttpConnection)Connector.open(promoUrl + StringUtil.replace(keywords," ", "%20"));
            parse(hc.openInputStream());

            initialiseForm();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try { if (hc!=null) hc.close(); }
            catch(IOException ignored) {}
        }
    }

    private void parse(InputStream is) {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
            SAXParser saxParser = factory.newSAXParser();
            InputSource inputSource = new InputSource(is);
            saxParser.parse(is,new XmlHandler(this));

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
...
&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;XmlHandler.java&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="java" name="code"&gt;
/*
 * To change this template, choose Tools  Templates
 * and open the template in the editor.
 */

package svdemo;

import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 *
 * @author ROMENL
 */
public class XmlHandler extends DefaultHandler {
    static String TAG_PROMOTION="Promotion";
    static String TAG_NAME="Name";
    static String TAG_DESCRIPTION="Description";
    static String TAG_VENUE="Venue";
    static String TAG_DATETIME="DateTime";
   
    private SvdemoWUITMidlet midlet;
    private PromoInfo promo=null;
    private Stack tagStack=new Stack();
    public XmlHandler(SvdemoWUITMidlet midlet) {
        this.midlet=midlet;
    }

    public void startDocument() throws SAXException {}
    public void startElement(String uri, String localName, String qName,
    Attributes attributes) throws SAXException {
        if(TAG_PROMOTION.equals(qName)) {
            promo=new PromoInfo();
        }
        tagStack.push(qName);
    }
    public void characters(char[] ch, int start, int length)
            throws SAXException  {
        String chars = new String(ch, start, length).trim();

        if(chars.length() &gt; 0) {
            String qName = (String)tagStack.peek();
     
            if (TAG_NAME.equals(qName)) {
                promo.setName(chars);
            } else if(TAG_DESCRIPTION.equals(qName)){
                promo.setDescription(chars);
            } else if(TAG_VENUE.equals(qName)) {
                promo.setVenue(chars);
            } else if(TAG_DATETIME.equals(qName)) {
                promo.setDateTime(chars);
            }
        }
    }

    public void endElement(String uri, String localName, String qName,
    Attributes attributes) throws SAXException  {
        tagStack.pop();
    }

    public void endDocument() throws SAXException {
        midlet.setPromo(promo);
    }
}
&lt;/pre&gt;
&lt;p&gt;Finally, presenting the retrieved data on the mobile device - the &lt;code&gt;initialiseForm()&lt;/code&gt; method in the MIDlet:&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
    private void initialiseForm() {
        Form f = new Form("JME LWUIT WS Demo!");
        
        f.setTransitionInAnimator(Transition3D.createCube(2000, false));
        f.setTransitionOutAnimator(Transition3D.createCube(2000, true));
        f.getStyle().setBgColor(0x0000ff);
        
        if(promo!=null) {
            f.addComponent(createLabel("Name:"));
            f.addComponent(createTextArea(1, promo.getName()));

            f.addComponent(createLabel("Description:"));
            f.addComponent(createTextArea(2, promo.getDescription()));

            f.addComponent(createLabel("Venue:"));
            f.addComponent(createTextArea(1, promo.getVenue()));

            f.addComponent(createLabel("Date:"));
            f.addComponent(createTextArea(1, promo.getDateTime()));
        }
        f.show();
      
        Command exitCommand = new Command("Exit");
        f.addCommand(exitCommand);
        f.setCommandListener(this);
    }
    
    private Label createLabel(String labelText) {
        Label label=new Label(labelText);
        Font font=Font.createSystemFont(Font.FACE_SYSTEM, 
                Font.STYLE_BOLD, Font.SIZE_MEDIUM);
        label.getStyle().setFont(font);
        return label;
    }
    
    private TextArea createTextArea(int rows, String text) {
        TextArea ta=new TextArea(rows ,20, TextArea.ANY);
        ta.setEditable(false);
        ta.setText(text);
        return ta;
    }
&lt;/pre&gt;
&lt;p&gt;
The following screenshots show the LWUIT 3D cube transition effect and the MIDlet output form respectively.&lt;/p&gt;
&lt;a href="http://1.bp.blogspot.com/_ILzIJXnrA40/SKrUcthONUI/AAAAAAAAABc/X2fIHgIO89o/s1600-h/snaggit.PNG"&gt;&lt;img id="BLOGGER_PHOTO_ID_5236231106491987266" style="CURSOR: hand" alt="" src="http://1.bp.blogspot.com/_ILzIJXnrA40/SKrUcthONUI/AAAAAAAAABc/X2fIHgIO89o/s200/snaggit.PNG" border="0" /&gt;&lt;/a&gt;
&lt;a href="http://2.bp.blogspot.com/_ILzIJXnrA40/SKrVFEdlYzI/AAAAAAAAABk/dnM1RljnaBc/s1600-h/snaggit.PNG"&gt;&lt;img style="cursor:pointer; cursor:hand;" src="http://2.bp.blogspot.com/_ILzIJXnrA40/SKrVFEdlYzI/AAAAAAAAABk/dnM1RljnaBc/s400/snaggit.PNG" border="0" alt=""id="BLOGGER_PHOTO_ID_5236231799845511986" /&gt;&lt;/a&gt;
&lt;p&gt;
&lt;span style="font-weight:bold;"&gt;Related Posts:&lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/10/consuming-restful-service-with-erlang.html"&gt;Consuming RESTful Service with Erlang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-from-android.html"&gt;Consuming Web Services from Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-wcf-web-service-using-groovy.html"&gt;Consuming WCF Web Service Using Groovy Client&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;Consuming WCF Web Service Using Java Client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-2296919955323679373?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/2296919955323679373/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=2296919955323679373' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2296919955323679373'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2296919955323679373'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/consuming-web-services-by-javame.html' title='Consuming Web Services By JavaME'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_ILzIJXnrA40/SKrFs1MfjmI/AAAAAAAAABU/HExBablwK98/s72-c/snaggit.PNG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-2656420513649579993</id><published>2008-08-18T12:05:00.016+10:00</published><updated>2008-10-09T23:08:08.897+11:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='Web Service'/><category scheme='http://www.blogger.com/atom/ns#' term='SOA'/><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='WCF'/><title type='text'>Consuming WCF Web Service Using Groovy Client</title><content type='html'>&lt;p&gt;Last month, I developed some simple &lt;a href="http://romenlaw.blogspot.com/2008/07/wcf-is-so-easy.html"&gt;web services using WCF &lt;/a&gt;and consumed it using &lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;various web service frameworks&lt;/a&gt; in Java. Now I have implemented the web service client using &lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;GroovyWS&lt;/a&gt;, which is an add-on module to &lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt;. It uses &lt;a href="http://incubator.apache.org/cxf/"&gt;CXF &lt;/a&gt;under the hood.&lt;/p&gt;
&lt;p&gt;To add the GroovyWS library, I simply copied its JAR file &lt;code&gt;groovy-all-1.5.6.jar&lt;/code&gt; into the Groovy's &lt;code&gt;lib&lt;/code&gt; directory (it can either go in &lt;code&gt;{groovy.home}/lib&lt;/code&gt; or &lt;code&gt;{user.home}/.groovy/lib&lt;/code&gt; according to the &lt;code&gt;groovy-starter.conf&lt;/code&gt; file). Then run the following script from Groovy Console:&lt;/p&gt;
&lt;pre name="code" class="groovy"&gt;
import groovyx.net.ws.WSClient
def proxy = new WSClient("http://localhost/PromoService.svc?wsdl", 
                this.class.classLoader)
        
def keywords = proxy.GetKeywordsFromMySpace("http://someurl/somepage.htm")
if(keywords!=null) {
    if (proxy.HasPromo(keywords)) {
        def promo = proxy.GetFirstPromo(keywords)
        println "Promo: ${promo?.promoName?.getValue()}, ${promo?.promoDateTime}"
    }
}
&lt;/pre&gt;
gives the result:
&lt;pre&gt;
BindingInfo = [BindingInfo http://schemas.xmlsoap.org/wsdl/soap/]
o = SOAPBinding ({http://schemas.xmlsoap.org/wsdl/soap/}binding):
required=null
transportURI=http://schemas.xmlsoap.org/soap/http
Promo: Batman The Dark Knight, 2008-07-30T10:00:00
&lt;/pre&gt;
&lt;p&gt;That's all!&lt;/p&gt;
&lt;p&gt;I then re-did it using Eclipse 3.4 with &lt;a href="http://groovy.codehaus.org/Eclipse+Plugin"&gt;Groovy Plug-in &lt;/a&gt;to create the Groovy project by following the instructions from the &lt;a href="http://groovy.codehaus.org/Eclipse+Plugin"&gt;same web site&lt;/a&gt;. Then I added the JAR files into the dependency:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ant.jar&lt;/code&gt; from Apache Ant 1.7.0 - this is needed by GroovyWS but not included in its distribution&lt;/li&gt;
&lt;li&gt;&lt;code&gt;groovy-all-1.5.6.jar&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
The Groovy code for the test client is very simple:
&lt;pre name="code" class="groovy"&gt;
package svdemo

import groovyx.net.ws.WSClient

/**
 * @author ROMENL
 *
 */
public class Program{

 /**
  * @param args
  */
 public static void main(def args){
  def proxy = new WSClient("http://localhost/PromoService.svc?wsdl", 
    Program.class.classLoader)
  
  def keywords = proxy.GetKeywordsFromMySpace("http://someurl/somepage.htm")
  if(keywords!=null) {
   if (proxy.HasPromo(keywords)) {
    def promo = proxy.GetFirstPromo(keywords)
    println "Promo: ${promo?.promoName?.getValue()}, ${promo?.promoDateTime}"
   }
  }
 }
}
&lt;/pre&gt;
Compiling then running the program yielded:
&lt;pre&gt;
18/08/2008 12:19:33 PM org.apache.cxf.endpoint.dynamic.DynamicClientFactory outputDebug
INFO: Created classes: org.tempuri.GetFirstPromo, org.tempuri.GetFirstPromoResponse, org.tempuri.GetKeywordsFromMySpace, org.tempuri.GetKeywordsFromMySpaceResponse, org.tempuri.HasPromo, org.tempuri.HasPromoResponse, org.tempuri.ObjectFactory, com.microsoft.schemas._2003._10.serialization.ObjectFactory, org.datacontract.schemas._2004._07.svdemo.ObjectFactory, org.datacontract.schemas._2004._07.svdemo.PromoInfo
BindingInfo = [BindingInfo http://schemas.xmlsoap.org/wsdl/soap/]
o = SOAPBinding ({http://schemas.xmlsoap.org/wsdl/soap/}binding):
required=null
transportURI=http://schemas.xmlsoap.org/soap/http
Promo: Batman The Dark Knight, 2008-07-30T10:00:00
&lt;/pre&gt;
&lt;p&gt;Much of the output was produced by CXF. Only the last line was produced by the test program. As you can see, the major difference between the Groovy version and the Java version using CXF is that in Java, you will use the &lt;code&gt;wsdl2java&lt;/code&gt; tool to generate the Java client proxy and the data contract classes up front; but in Groovy, all that is automatic when you run the script. This makes the Groovy code very simple and small. However, the downside of this is that the IDE (Eclipse) cannot know the methods and members of the class up front. Therefore, there is no code-completion (intellisense) support for the business methods. Since I heavily rely on code-completion of the IDEs, this is a big disadvantage about dynamic languages for me (I do not memorise all the class members and methods names, and I do not read all the API docs either. I usually try to figure out the methods I need by looking at their names).&lt;/p&gt;
&lt;p&gt;There are a couple of things I had to do for my code to run without errors:
&lt;ol&gt;
&lt;li&gt;I had to upgrade my JRE used by Eclipse to a &lt;code&gt;1.6.0_u10rc&lt;/code&gt;. Eclipse automatically addes a bunch of JARs from the JRE, including the &lt;code&gt;resources.jar&lt;/code&gt;, which includes the JAXB classes. In an earlier version of my JRE (1.6.0_u2) the JAXB was version 2.0, but the CXF bundled in GroovyWS 0.3.1 requires v2.1. However, if you are not using Eclipse (e.g. using the Groovy Console directly), this step is not  required&lt;/li&gt;
&lt;li&gt;The CXF bundled with GroovyWS 0.3.1 had problem generating the code from WSDL - it complained about a namespace &lt;code&gt;"doesnt contain ObjectFactory.class or jaxb.index"&lt;/code&gt;. It turned out to be a CXF bug and had been fixed in CXF 2.1.1, thanks to &lt;a href="http://marc.info/?l=groovy-user&amp;m=121777001001742&amp;w=2"&gt;a post by Bernie&lt;/a&gt;. After I replaced the &lt;code&gt;org/apache/cxf&lt;/code&gt; branch of the &lt;code&gt;groovyws-all-0.3.1.jar &lt;/code&gt;using the contents in &lt;code&gt;cxf-2.1.1.jar&lt;/code&gt;, it was smooth sailing.&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;span style="font-weight:bold;"&gt;Related Posts:&lt;/span&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/10/consuming-restful-service-with-erlang.html"&gt;Consuming RESTful Service with Erlang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-from-android.html"&gt;Consuming Web Services from Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/08/consuming-web-services-by-javame.html"&gt;Consuming Web Services By JavaME&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://romenlaw.blogspot.com/2008/07/consuming-wcf-web-service-using-java.html"&gt;Consuming WCF Web Service Using Java Client&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-2656420513649579993?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/2656420513649579993/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=2656420513649579993' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2656420513649579993'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2656420513649579993'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/consuming-wcf-web-service-using-groovy.html' title='Consuming WCF Web Service Using Groovy Client'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8019782047390849133</id><published>2008-08-16T21:18:00.005+10:00</published><updated>2008-08-16T22:56:37.042+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><category scheme='http://www.blogger.com/atom/ns#' term='Database'/><category scheme='http://www.blogger.com/atom/ns#' term='Design'/><category scheme='http://www.blogger.com/atom/ns#' term='ORM'/><title type='text'>ORM Frameworks and DBA Friendliness</title><content type='html'>&lt;p&gt;Database programming used to directly rely on tools provided by the database vendors, such as &lt;a href="http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.esqlc.doc/esqlc.htm"&gt;ESQL-C&lt;/a&gt; from Informix, &lt;a href="http://en.wikipedia.org/wiki/Pro*C"&gt;Pro*C&lt;/a&gt; from Oracle and Sybase. Using these tools makes the business logic (C/C++ code) mingled with and dependent on the database design and logic (SQL code).&lt;/p&gt;
&lt;p&gt;The emergence of &lt;a href="http://en.wikipedia.org/wiki/Object-relational_mapping"&gt;Object-Relational Mapping (ORM)&lt;/a&gt; frameworks solved this problem by decoupling the code and the database assets. Nowadays, when designing software, it's not a matter of using ORM or not for the designer, but rather which ORM framework to use.&lt;/p&gt;
&lt;p&gt;Most ORM or entity frameworks (&lt;a href="http://www.hibernate.org/"&gt;Hibernate&lt;/a&gt;, &lt;a href="http://java.sun.com/javaee/overview/faq/persistence.jsp"&gt;JPA&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/ADO.NET_Entity_Framework"&gt;Entity Framework&lt;/a&gt;) out there generate SQL statements automatically and hide the internals from the developers and rightly so. However, this creates another problem: to optimise the SQL statements, it is usually a joint effort between the developers and DBAs (or data architects) - the DBA should have the final say in how to shape the SQL queries and to design/modify the database schema to suit the usage pattern of the application(s). Now that the SQL statements are auto-generated by the framework and the DBA cannot design or examine the SQL code up front, the control of the DBA is diminished.&lt;/p&gt;
&lt;p&gt;This loss of control may not be a problem for custom-built/one-time applications, or applications with a simply data model. However, if you are dealing with a large/complex product that has to be implemented in different environments, then it is crucial to have full control over the database deisgn and deployment and how it is to be used.&lt;/p&gt;
&lt;p&gt;To have the visibility to the SQL statements up front, there seems to be two options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;resort back to JDBC/ADO.NET... to mix the SQL statements with the business logic code - not advisable&lt;/li&gt;
&lt;li&gt;use a framework that exposes the SQL statements - &lt;a href="http://ibatis.apache.org/"&gt;iBATIS SQLMap&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Strictly speaking, iBATIS is not an ORM framework - it maps the objects with SQL statement query input/output. There are two distinct advantages of using SQL Map:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The database-specific code (SQL statements, connection strings, etc.) can be centralised and totally separated from the core (Java/C#...) code. This makes the code decoupled from the database and more portable across different databases;&lt;/li&gt;
&lt;li&gt;All database CRUD are specified in native SQL which are DBA friendly, making it easy for the DBA to review and participate in the design of these statements.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because iBATIS does not map objects with tables, it makes it ideal for applicaitons that have to use legacy databases which did not adhere to object oriented design or have totally different structure from the object model.&lt;/p&gt;
&lt;p&gt;Therefore, if you are involved in software product development (as opposed to custom-built one-time projects), DBA friendliness should be a factor to be considered when deciding an ORM framework.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8019782047390849133?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8019782047390849133/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8019782047390849133' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8019782047390849133'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8019782047390849133'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/orm-frameworks-and-dba-friendliness.html' title='ORM Frameworks and DBA Friendliness'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-4985037909034291908</id><published>2008-08-12T11:29:00.010+10:00</published><updated>2008-08-12T14:38:51.926+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><title type='text'>The Life and Death of OC4J</title><content type='html'>&lt;p&gt;Once upon a time, two smart Swedes wrote a J2EE server called &lt;a href="http://orionserver.com/"&gt;Orion &lt;/a&gt;and formed a company Ironflare to sell it. Orion server was totally different from all the other J2EE containers out there: it's light weight, blazing fast and dirt cheap (only a couple of grand USD for a commercial license, which was unheard of in the heyday of IT and internet)! It soon gained traction and almost gained a cult status in the J2EE world.&lt;/p&gt;
&lt;p&gt;Then came the 21st century. In 2001, Oracle licensed Orion and created &lt;a href="http://www.oracle.com/technology/tech/java/oc4j/index.html"&gt;OC4J&lt;/a&gt;. The two smart Swedes from Ironflare moved with it and worked on OC4J for Oracle. Therefore, the Orion server and the website was virtually dead. However, Orion's heart still beats inside OC4J's body - you could even see the words 'orion' inside some of OC4J's configuration files. OC4J then became the core of Oracle Application Server for over half a decade, during which time the Oracle AS had grown to a humongous beast.&lt;/p&gt;
&lt;p&gt;In January 2008, Oracle announced the &lt;a href="http://www.oracle.com/corporate/press/2008_apr/bea-closes-rls.html"&gt;merger with BEA&lt;/a&gt;, which created great FUD among OC4J and Weblogic communities - they were speculating whether Oracle will kill off Weblogic or OC4J. Yesterday, Oracle finally sounded the death knell for OC4J by announcing the release of &lt;a href="http://www.oracle.com/corporate/press/2008_aug/wls-nr-103.html"&gt;Oracle Weblogic Server 10g R3&lt;/a&gt; - it threw its weight behind WLS and is abandoning OC4J. This will no doubt create panick among OC4J user communities. I worked for a company whose core technology was Oracle Forms (yes, you heard that right!) and they used to be relatively calm about it because Oracle offered a migration path from Oracle Forms to its JSF framework, and for the meantime you can still run the Oracle Forms on Oracle AS 9i as Java applets (no longer supported in 10g though). Now that OC4J will disappear, that will certainly motivate them to migrate off the Oracle Forms technology sooner.&lt;/p&gt;
&lt;p&gt;I don't believe the two smart Swedes will go back to resurrect Orion server because there are several free JavaEE servers nowadays, which take away the incentive for people to use Orion. Besides, Orion has been disconnected with the Java community for so long, it's fan base has all but evaporated.&lt;/p&gt;
&lt;img style="float:left; margin:0 10px 10px 0;cursor:pointer;" src="http://www.clipartheaven.com/clipart/holidays/halloween/tombstone-clipart.gif" border="0" width="100" height="100" alt="" /&gt;Orion &amp; OC4J&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-4985037909034291908?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/4985037909034291908/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=4985037909034291908' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4985037909034291908'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/4985037909034291908'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/life-and-death-of-oc4j.html' title='The Life and Death of OC4J'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7199082579940400756</id><published>2008-08-11T14:23:00.008+10:00</published><updated>2008-08-13T10:05:16.682+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>Beijing 2008</title><content type='html'>&lt;p&gt;The Beijing 2008 Olympic opened with the most &lt;a href="http://picasaweb.google.com.au/romen.law/Beijing2008Opening"&gt;spectacular opening ceremony &lt;/a&gt;ever. It was China's coming-out party and China spared no effort and expense to impress the world - and indeed it did.&lt;/p&gt;
&lt;p&gt;To me, the weeekend was pretty much an Olympic weekend - watching the events on TV and internet, reading the latest news and results from the &lt;a href="http://www.beijing2008.cn/"&gt;Beijing2008.com&lt;/a&gt; website. &lt;/p&gt;
&lt;P&gt;The website is managed by China's largest information portal - &lt;a href="http://www.sohu.com"&gt;sohu.com&lt;/a&gt;. Sohu deployed 600 quad-core Intel Xeon powered servers to host the website, plus 300 more as backup servers. The massive infrastructure supports millions of concurrent users and billions of hits a day. On the software side, they used Apache 2.2.3 running on Red Hat Linux and the web technology is the good old shtml! This is an interesting software stack and all open source and free. It's no secret that the Chinese government favours Linux - who doesn't?! The combination of shtml over Apache is quite appropriate for websites like this: contents are manually input and updated; the need for a database is very low.&lt;/p&gt;
&lt;p&gt;shtml is good for using with template pages and then dynamically include the corresponding contents from other files. There is really no need for a full-blown database server for an Olympic website - the Game results are not particularly large or complicated, they are not even uniformly structured (each event has its own rules and ways of expressing the results). All you need is a good content management system (and indeed Sohu used one, which likely uses a database server itself) due to the frequent and current updates to the contents and their distribution to the large number of servers.&lt;/p&gt;
&lt;p&gt;I still remember the Sydney 2000 Games where IBM was contracted to provide the IT infrastructure. Unsurprisingly, IBM used Websphere and Java technology to implement the official website. With such resource-hungry beasts, I'd image they needed at least double the amount of hardware to support the same level of service (even if they were as powerful as the quad-core Xeon servers).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7199082579940400756?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7199082579940400756/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7199082579940400756' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7199082579940400756'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7199082579940400756'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/beijing-2008.html' title='&lt;img height=&quot;62&quot; src=&quot;http://www.beijing2008.cn/upload/cms_owrp2/homepage_cn/08new_beijing_logo.gif&quot; width=&quot;55&quot;/&gt;Beijing 2008'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-2119955763731726022</id><published>2008-08-08T14:18:00.007+10:00</published><updated>2008-08-13T10:10:58.268+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><title type='text'>Killer Frameworks vs. Killer Apps</title><content type='html'>&lt;p&gt;I came across &lt;a href="http://java.dzone.com/news/better-faster-lighter-componen"&gt;this post &lt;/a&gt; on Java Lobby the other day, which put a grin on my face. Basically the author of the post was questioning how come there are so many programming libraries and frameworks out there and people talk about them so much, but no one seem to talk about business applications with the same level of enthusiasm.&lt;/p&gt;
&lt;p&gt;This phenomenon is something that I observe everyday yet never realised it until reading the post. Now that I come to think about it, I can find several reasons for this.&lt;/p&gt;
&lt;p&gt;First of all, the web sites such as Java Lobby cater for the software developer communities. Software developers tend to value technical knowledge more than business (domain) knowledge. After all, domain knowledge experts are usually titled business analysts, subject matter experts, and the like. The role of developers is mainly solving problems with technical tools. If the same business problem can be solved by non-technical means, e.g. lobbying, re-organising, developers are kept out of the exercise. So it's no wonder developers talk about technical tools all the time.&lt;/p&gt;
&lt;p&gt;Of course, there are developers who are also interested in business knowledge and excel in it. However, the very nature of domain knowledge is, well... domain-specific. This means that what you know and what you blog about may not be understood or appreciated by the rest of the developer community. Therefore, this kind of discussions are rare. (When I tell people that I work in the Telco industry in a birthday party, they automatically assume that I know how to install telephone lines into their houses).&lt;/p&gt;
&lt;p&gt;Also, the IT industry is very dynamic and people move around fairly frequently. So after such moves, the domain knowledge may also change and new killer app ideas will germinate and the old killer app ideas wither. Without longevity, it is pretty hard to grow an idea into a full-blown product.&lt;/p&gt;
&lt;p&gt;Another reason for the phenomenon is that discussion about business products are not as generic as talking about technology such as Java which actually covers many topics. By comparison, the area covered by a business product is very small. I am sure you can find blogs about great gaming ideas on gaming sites. But I doubt that the same gaming site also talk about billing applications. Therefore, their audience is also quite limited.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-2119955763731726022?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/2119955763731726022/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=2119955763731726022' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2119955763731726022'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/2119955763731726022'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/killer-frameworks-vs-killer-apps.html' title='Killer Frameworks vs. Killer Apps'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-7002123209303433141</id><published>2008-08-05T22:38:00.006+10:00</published><updated>2008-08-25T10:46:13.825+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='JavaFX'/><category scheme='http://www.blogger.com/atom/ns#' term='Design'/><title type='text'>Why Not Five-teen?</title><content type='html'>&lt;p&gt;My four-year-old son has just learnt how to count. As a newbie, he has not memorised all the numbers. So sometimes, he improvises and derives the number words that he needed. For example, when he sees '15', he would read it as 'five-teen'. He is OK with the numbers that are 'regular', such as 14, 16, 17... But he could not readily read the irregular ones all the time, such as 11 and 12.&lt;/p&gt;
&lt;p&gt;At this young age, he has not been 'tainted' by all the variations and irregularities of the English spelling and pronunciation rules. It is natural that he follows the 'logical' way to come up what he thinks and seemingly to be  correct.&lt;/p&gt;
&lt;p&gt;The English language is full of exceptions and variations in its rules of spelling, pronunciation and grammar. These inconsistencies make English not an easylanguage to learn comparing to many others.&lt;/p&gt;
&lt;p&gt;When it comes to computing languages and APIs, consistency also determines whether the language or API is easy to learn and use (although now people are talking about more advanced topics, such as &lt;a href="http://www.javaworld.com/javaworld/jw-07-2008/jw-07-dsls-in-java-2.html"&gt;fluency&lt;/a&gt;). Let's take GUI frameworks as an example.&lt;/p&gt;
&lt;p&gt;When building graphical user interfaces, we need to put some widgets/components onto the canvas or another widget. So there is a parent-child relationship: the parent is the container, the child is the object being put inside the container. There are two ways of saying this relationship:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;parent.addChild(child)&lt;/li&gt;
&lt;li&gt;child.setParent(parent) or specify the parent in the child's constructor&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most GUI frameworks use the first syntax - such as &lt;a href="http://en.wikipedia.org/wiki/Swing_(Java)"&gt;Swing&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Windows_Forms"&gt;Windows Forms&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Windows_Presentation_Foundation"&gt;WPF&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/ASP.NET"&gt;ASP.NET&lt;/a&gt;, &lt;a href="http://en.wikipedia.org/wiki/Google_Web_Toolkit"&gt;GWT&lt;/a&gt;, &lt;a href="http://echo.nextapp.com/site/echo2"&gt;Echo2&lt;/a&gt;. For example, in Swing:&lt;/p&gt;
&lt;pre class="java" name="code"&gt;
JButton button=new JButton("Clear");
button.setActionCommand(command);
button.setToolTipText(tooltip);
button.addActionListener(this);
...
JPanel buttonPanel=new JPanel(new FlowLayout());
buttonPanel.add(button);
&lt;/pre&gt;
&lt;p&gt;In these GUI frameworks, the same widget cannot be added to more than one container. If you did, then it either complains at runtime or gives undesired odd results. But the API allows you to make these mistakes and your code compiles without error. So this is an inconsistency in those framework APIs.&lt;/p&gt;
&lt;p&gt;To fix this problem, &lt;a href="http://en.wikipedia.org/wiki/JFace"&gt;SWT/JFace &lt;/a&gt;takes the second approach by using the constructor:&lt;/p&gt;
&lt;pre class="java" name="code"&gt;
Composite buttons=new Composite(this, SWT.NONE);
...
Button clearButton=new Button(buttons, SWT.PUSH);
&lt;/pre&gt;
&lt;p&gt;This way, you cannot mistakenly set more than one parent for the widget.&lt;/p&gt;
&lt;p&gt;There are other languages and frameworks that take a third approach by creating the child widget inside the code block of the parent. For example, in &lt;a href="http://wicket.apache.org/"&gt;Wicket&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="java" name="code"&gt;
public abstract class BaseAddressForm extends Form {
...
  void initForm() {
  ...
    add(new Button("clear", new Model("Clear")) {
      @Override
      public void onSubmit() {
        log.info(getModelObject());
        resetModel();
      }
    });
  }
...
}
&lt;/pre&gt;
Similarly, in &lt;a href="http://www.sun.com/software/javafx/"&gt;JavaFX &lt;/a&gt;script:
&lt;pre class="ruby" name="code"&gt;
...
bottom: FlowPanel {
  content: [
    Button {
      text: "Clear"
      action: operation() {
        model.selectedAddressIndex=-1;
      }
    },
...
&lt;/pre&gt;
&lt;p&gt;The above are exmaples within a single framework. When we have to deal with multiple frameworks the problem becomes more obvious. Normally, we don't expect different frameworks/languages to follow the same rules. However, when one framework is an add-on/extension to another, our expectation changes. For example, &lt;a href="http://gwt-ext.com/"&gt;GWT-Ext &lt;/a&gt;is an add-on to GWT. However, its API naming conventions are not consistent with that of GWT: in GWT the method name &lt;em&gt;getText() &lt;/em&gt;is used to get the text label of a widget (e.g. a label, button, etc.); but in GWT-Ext the method is called &lt;em&gt;getValue()&lt;/em&gt;. Also, in GWT, the &lt;em&gt;Command &lt;/em&gt;is widely used (in a similar way to the &lt;em&gt;Action &lt;/em&gt;class in JFace). However, in GWT-Ext event listeners have to be explicitly created...&lt;/p&gt;
&lt;p&gt;Don't get me wrong, I love using GWT and GWT-Ext. But these little inconsistencies hinder productivity when using this mix.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-7002123209303433141?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/7002123209303433141/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=7002123209303433141' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7002123209303433141'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/7002123209303433141'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/why-not-five-teen.html' title='Why Not Five-teen?'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-8985792473293560260</id><published>2008-08-02T14:11:00.019+10:00</published><updated>2008-08-25T11:02:53.277+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Web Service'/><category scheme='http://www.blogger.com/atom/ns#' term='SOA'/><category scheme='http://www.blogger.com/atom/ns#' term='REST'/><category scheme='http://www.blogger.com/atom/ns#' term='web'/><title type='text'>RESTful Web Service</title><content type='html'>&lt;p&gt;I built a &lt;a href="http://romenlaw.blogspot.com/2008/07/wcf-is-so-easy.html"&gt;SOAP web service using WCF &lt;/a&gt;as a part of a demo. However, as it turned out, the client software I was given can only handle HTTP as transport (the WCF service I did accepts requests directly from TCP payload). So I had to convert it into a &lt;a href="http://en.wikipedia.org/wiki/Representational_State_Transfer"&gt;RESTful &lt;/a&gt;web services. I have basicly a couple of options to implement the RESTful web services:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Modify the existing WCF project to support REST style according to &lt;a href="http://msdn.microsoft.com/en-us/library/aa395208.aspx"&gt;MSDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create a ASP.NET project and reuse the code that I created in the WCF project.&lt;/li&gt;
&lt;li&gt;Create a Java web project and rewrite the code.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The 1st option seems the best choice on the surface. But there are still too much of hand-rolling of boiler-plate type of code to be done in option 1. You have to process the HTTP request method using if/switch ... yuck.&lt;/p&gt;
&lt;p&gt;The 2nd option will save me some time because I can reuse the LINQ to XML code. But the code is very short, so the saving is not big. Again, the disadvantage of this option is that I have to create all the virtual path mapping (extending the VirtualPathProvider class) myself. The time to spend on building the ASP.NET web application from scratch will outweigh the savings on reusing the bit of XML query code (although there is a pretty good &lt;a href="http://blogs.msdn.com/dseven/attachment/1643858.ashx"&gt;sample &lt;/a&gt;by &lt;a href="http://blogs.msdn.com/dseven/archive/2007/02/10/boise-code-camp-enabling-rest-in-asp-net.aspx"&gt;Doug Seven&lt;/a&gt; from where I can steal some code.)&lt;/p&gt;
&lt;p&gt;NetBeans (v6.1) has a RESTful web services (&lt;a href="https://jsr311.dev.java.net/"&gt;JSR-311&lt;/a&gt;) plugin using &lt;a href="https://jersey.dev.java.net/"&gt;Jersey &lt;/a&gt;libraries. The wizard can create all the boilerplate codes and configurations required to get a RESTful service up and running by a few simple steps. The downside of this approach is that I have to rewrite the web service implementation methods - XML query, HTML scraping... However, the code for those is quite simple and should be easy to rewrite.&lt;/p&gt;
&lt;p&gt;So, ASP.NET is lagging behind in the area of RESTful web services. I guess to get the similar features offered by JSR-311 and NetBeans, one has to wait for the upcoming &lt;a href="http://weblogs.asp.net/scottgu/archive/2007/10/14/asp-net-mvc-framework.aspx"&gt;MVC Framework for ASP.NET&lt;/a&gt;. An excellent tutorial and example project using the MVC Framework can be found &lt;a href="http://weblogs.asp.net/scottgu/archive/2007/11/13/asp-net-mvc-framework-part-1.aspx"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since time is of essense in this exercise, I chose option 3.&lt;/p&gt;
&lt;p&gt;I started by creating a Java Web Application (called &lt;em&gt;SvdemoRestful&lt;/em&gt;) in NetBeans. Then adding a RESTful web service is simply right-clicking the project and choose &lt;em&gt;New -&gt; RESTful Web Services from Patterns... &lt;/em&gt;NetBeans also offers a wizard to create RESTful Web Services from Entity Class. I did not need to use Entity Class because I am going to use XPath to query a simple XML file (as my database) and return the same XML snippet to the service consumer (i.e. I don't even need to convert the XML into a Java bean);and I don't want to use the ORM tools and create Persistent Objects. Then it's just a matter of filling in the self-explanatory fields in the wizard.&lt;/p&gt;
&lt;p&gt;Once this is done, the skeleton code is generated along with all the necessary web configuration settings (e.g. url mapping). I want to have my RESTful service to have URI like below so that the GET method will return the first promotion that matches the keywords (actually, it is better to use query parameters instead of path parameters for keyword searching services):&lt;/p&gt;
&lt;pre name="code"&gt;
http://hostname/SvdemoRestful/resources/promo/jazz, action
&lt;/pre&gt;
&lt;p&gt;To achieve this, the @Path annotation is defined as
&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
@Path("promo/{keywords}")
&lt;/pre&gt;
&lt;p&gt;I used XPath to retrieve the Promotion record from the &lt;a href="http://romenlaw.blogspot.com/2008/07/wcf-is-so-easy.html"&gt;data.xml &lt;/a&gt;file. I need to make the keyword matching case in-sensitive. In XPath 2.0 there is an &lt;a href="http://www.w3schools.com/Xpath/xpath_functions.asp"&gt;upper-case() function&lt;/a&gt;, but it does not seem to be supported by the XPath libraries. So I have to use the following XPath statement:&lt;/p&gt;
&lt;pre name="code"&gt;
//Promotion[contains(translate(Tags,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'),'keywordsToBeMatched')]
&lt;/pre&gt;
&lt;p&gt;as opposed to &lt;/p&gt;
&lt;pre name="code"&gt;
//Promotion[contains(upper-case(Tags),'keywordsToBeMatched')]
&lt;/pre&gt;
&lt;p&gt;So the resulting code for the promo RESTful web service:&lt;/p&gt;
&lt;pre name="code" class="java"&gt;
/*
 *  PromoResource
 *
 */

package svdemo;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.ConsumeMime;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Node;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

/**
 * REST Web Service
 *
 * @author ROMENL
 */

@Path("promo")
public class PromoResource {
    static String EMPTY="&lt;empty/&gt;";
    @Context
    private UriInfo context;

    /** Creates a new instance of PromoResource */
    public PromoResource() {
    }

    /**
     * Retrieves representation of an instance of svdemo.PromoResource
     * The URL parameters are:
     * keywords - comma delimited words to be matched
     * @return an xml string representing the PromoInfo
     */
    @GET
    @Path("{keywords}")
    @ProduceMime("text/xml")
    public String getXml(@PathParam("keywords") String keywords ) {
        if (keywords == null || keywords.length()==0) {
            return EMPTY;
        }
        String[] words = keywords.split(",");
        try {
            DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
            domFactory.setNamespaceAware(true);
            DocumentBuilder builder = domFactory.newDocumentBuilder();
            Document doc =  builder.parse("data.xml");
            
            XPathFactory factory = XPathFactory.newInstance();
            XPath xpath = factory.newXPath();
            for(String keyword : words) {
                XPathExpression expr = xpath.compile(
                        "//Promotion[contains(translate(Tags,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'),'"
                        +keyword.trim().toUpperCase()+"')]");
                Node result = (Node)expr.evaluate(doc, XPathConstants.NODE);
                if(result!=null) {
                    StringBuffer sb=new StringBuffer("&lt;Promotion&gt;");
                    NodeList nodes = result.getChildNodes();
                    for(int i=0; i&amp;lt;nodes.getLength(); i++) {
                        Node node = nodes.item(i);
                        if(!"#text".equals(node.getNodeName())){
                            sb.append("&lt;"+node.getNodeName()+"&gt;");
                            sb.append(node.getTextContent());
                            sb.append("&lt;/"+node.getNodeName()+"&gt;");
                        }
                    }
                    sb.append("&lt;/Promotion&gt;");
                    return sb.toString();
                }
            }
            return EMPTY;
        } catch (Exception ex) {
            Logger.getLogger(PromoResource.class.getName()).log(Level.SEVERE, null, ex);
            return EMPTY;
        }
    }

    /**
     * PUT method for updating or creating an instance of PromoResource
     * @param content representation for the resource
     * @return an HTTP response with content of the updated or created resource.
     */
    @PUT
    @ConsumeMime("application/xml")
    public void putXml(String content) {
    }
}
&lt;/pre&gt;
In another service I needed to pass in a URL so that the service will get the web page of the URL and scrape the HTML code (yes, scraping HTML is a bad practice, but I don't have any alternatives and it's OK for demo purposes). The HTML scraping is easily handled by using the &lt;em&gt;javax.swing.text.html.HTMLEditorKit&lt;/em&gt;. The problem is that the input parameter to this RESTful service contains forward-slashes ('/') and it will confuse the web server if I give the URLs like so:
&lt;pre name="code"&gt;
http://hostname/SvdemoRestful/resources/webPageKeywords/http://localhost/someWebPage.html
http://hostname/SvdemoRestful/resources/webPageKeywords/'http://localhost/someWebPage.html'
&lt;/pre&gt;
According to JSR-311, the above urls should be specified as @Path("webPageKeywords/{url:.+}"), but it did not work. I tried different values of the &lt;em&gt;limited &lt;/em&gt;and &lt;em&gt;encode &lt;/em&gt;attributes of the @Path(), but to no avail. So I had to resort to passing the parameters as the URL's query parameter:
&lt;pre name="code"&gt;
http://hostname/SvdemoRestful/resources/webPageKeywords?url=http://localhost/someWebPage.html
&lt;/pre&gt;
The corresponding @Path annotation is just @Path("webPageKeywords"). The corresponding service implementation code snippet:
&lt;pre name="code" class="java"&gt;
/**
 * REST Web Service
 *
 * @author ROMENL
 */

@Path("webPageKeywords")
public class WebPageKeywordsResource {
    @Context
    private UriInfo context;

    /** Creates a new instance of WebPageKeywordsResource */
    public WebPageKeywordsResource () {
    }

    /**
     * Retrieves representation of an instance of svdemo.WebPageKeywordsResource 
     * @return empty string if no keywords found; otherwise, comma delimited keywords
     */
    @GET
    @ProduceMime("text/plain")
    public String getText(@QueryParam("url") String urlString) {
        if(urlString==null || urlString.length()==0)
            return "";
        try {
            URL url = new URL(urlString);
            HTMLEditorKit kit = new HTMLEditorKit();
            HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
            doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE);
            Reader HTMLReader = new InputStreamReader(url.openConnection().getInputStream());     
            kit.read(HTMLReader, doc, 0);
            String keywords="";
            Element e=doc.getElement("ProfileMusic");
            if(e!=null) {
                keywords=doc.getText(e.getStartOffset(), e.getEndOffset()-e.getStartOffset());
            }
            e=doc.getElement("ProfileFilms");
            if(e!=null) {
                keywords+=","+doc.getText(e.getStartOffset(), e.getEndOffset()-e.getStartOffset());
            }
            return keywords;
        } catch (Exception ex) {
            Logger.getLogger(WebPageKeywordsResource .class.getName()).log(Level.SEVERE, null, ex);
            return "";
        }
    }

    /**
     * PUT method for updating or creating an instance of WebPageKeywordsResource 
     * @param content representation for the resource
     * @return an HTTP response with content of the updated or created resource.
     */
    @PUT
    @ConsumeMime("text/plain")
    public void putText(String content) {
    }
}
&lt;/pre&gt;
I am looking forward to the new ASP.NET MVC Framework and will definitely rewrite these services using it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-8985792473293560260?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/8985792473293560260/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=8985792473293560260' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8985792473293560260'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/8985792473293560260'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/08/restful-web-service.html' title='RESTful Web Service'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5245653944026204701</id><published>2008-07-30T20:16:00.009+10:00</published><updated>2008-08-25T11:03:16.028+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Telecommunications'/><category scheme='http://www.blogger.com/atom/ns#' term='Framework'/><title type='text'>Telco APIs</title><content type='html'>&lt;p&gt;The communications industry (telecommunications and internet) must have the largest number of standards specifications - from the layering of the network model, to individual protocols inside each layer. Even the same standard can be defined by more than one standard bodies - ITU-T, ESTI, ANSI, etc. The standards are very necessary in the communications industry. Afterall, communications are all about being able to interwork/interact with each other. So a common language is needed.&lt;/p&gt;
&lt;p&gt;Traditionally, the standards stayed at the protocol level - i.e. to ensure that different systems/subsystems can communicate with each other. Typical exmaples are SS7 (and SS7 based protocols), VoIP protocols (SIP, H.323). More recently, as the industry matures, standard programming APIs have become prevalent, e.g. &lt;a href="http://portal.etsi.org/docbox/TISPAN/Open/OSA/ParlayX21.html"&gt;Parlay/X&lt;/a&gt;, &lt;a href="http://www.tmforum.org/BestPracticesStandards/Downloads/2900/Home.html"&gt;OSS/J&lt;/a&gt;, &lt;a href="http://java.sun.com/products/jain/api_specs.html"&gt;JAIN APIs&lt;/a&gt;. These APIs enables inter-operability among different systems and system vendors. It also cuts down the effort of individual software vendors because much of the specification and documentation have already been done by the community/expert groups. Leveraging these standard APIs increases the software vendors' productivity and ensures smoother integration with other systems.&lt;/p&gt;
&lt;p&gt;I have been involved in service development using legacy Service Creation Environment (SCE), which provides proprietory drag-and-drop type of 'programming' environment (think Visio). The legacy SCE boasts that no programming experience is needed and only drawing of the flow diagram is required. On the surface, this seems an attractive paradigm (apart from lack of documentation - zero documentation really), however, once you need to go beyond the surface, it's nothing but pure frustration. When you are stuck, you have to ask the vendor to provide another service building block (SBB) in order to put it onto the flow diagram. On the contrary, in a JAIN SLEE environment, a developer can write new SBBs using Java following the JAIN APIs and architecture. This gives the developer power and flexibility over the legacy approach.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3000236639335370164-5245653944026204701?l=romenlaw.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://romenlaw.blogspot.com/feeds/5245653944026204701/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3000236639335370164&amp;postID=5245653944026204701' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5245653944026204701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3000236639335370164/posts/default/5245653944026204701'/><link rel='alternate' type='text/html' href='http://romenlaw.blogspot.com/2008/07/telco-apis.html' title='Telco APIs'/><author><name>Romen</name><uri>http://www.blogger.com/profile/02485809239634291945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='25' src='http://bp0.blogger.com/_ILzIJXnrA40/SEvDkjVRxRI/AAAAAAAAAAQ/jLh4RItPSac/S220/ramen.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3000236639335370164.post-5817951479881736355</id><published>2008-07-25T16:25:00.036+10:00</published><updated>2009-06-22T13:46:19.990+10:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Java'/><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='Web Service'/><category scheme='http://www.blogger.com/atom/ns#' term='SOA'/><category scheme='http://www.blogger.com/atom/ns#' term='WCF'/><title type='text'>Consuming WCF Web Service Using Java Client</title><content type='html'>&lt;strong&gt;[Updated on 2009-06-22]&lt;/strong&gt; &lt;em&gt;The JSE's native &lt;code&gt;wsimport&lt;/code&gt; tool has been added along with custom binding in a more recent post - &lt;a href="http://romenlaw.blogspot.com/2009/06/jaxb-custom-data-binding.html"&gt;JAXB Custom Data Binding&lt;/a&gt;&lt;/em&gt;.
&lt;p&gt;From the &lt;a href="http://romenlaw.blogspot.com/2008/07/wcf-is-so-easy.html"&gt;previous post &lt;/a&gt;I showed how to create a simple web service using WCF and consume it with a windows console application written in C#.&lt;/p&gt;
&lt;p&gt;To have the same web services consumed by a Java client, I needed to mak
