Friday 19 November 2010

SN 1979C

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 CHANDRA X-Ray Observatory site where the original news was released.

The 30-year number is the observed value 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.

What the article does not reveal is in what state the object was when it was first observed in 1979 – whether it had already turned into a supernova / black hole or still a star.

Sunday 26 September 2010

JAAS with Tomcat

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  steps involved to set up JAAS with Tomcat. Here, I outline these steps from a beginner’s perspective.

Step 1 – Implement the JASS interfaces.

The basic interfaces to be implemented are

  1. Principal – 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 getName() which returns the name of the user or role that it represents.
  2. LoginModule – this is the main class that usually serves as the wrapper for customised authentication logic. The login() method is where the customised login logic should be invoked.

Once implemented, JAR up these classes including the customised login classes and put it in $CATALINA_BASE/lib.

See the code segments at the end for example implementations.

Step 2 – Configure Tomcat Server

Edit $CATALINA_BASE/conf/server.xml to put in the JAASRealm configuration. As instructed by Tomcat’s Realm-HOW-TO documentation, this can be done at various levels - <Engine>, <Host> or <Context>. I added it inside the <Host> tag:



	
	
...

Notice the userClassNames and roleClassNames are the classes that were created in step 1.

Step 3 – The JRE login configuration

Create a JAAS Login Configuration file. I called it jaas.config and put it under $CATALINA_BASE/conf:
acczk {
  com.laws.acc.jaas.AccLoginModule required;
};

There are two ways to configure the JRE to load the login ocnfiguration file. I prefer to specify it in JAVA_OPTS. For Tomcat, this means to create a setenv.sh or setenv.bat in the $CATALINA_BASE/bin directory. Here is my setenv.bat:

set JAVA_OPTS=-Djava.security.auth.login.config==C:/Tools/apache-tomcat-7.0.2/conf/jaas.config

Step 4 – The Login Form

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 login.zul:






Welcome to ${c:l('client_name')}!

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(); } } Username : Password :
${errMsg}
j_username.focus();
The important part here is the HTML form employing j_username, j_password and j_security_check.

Step 5 - Configure web.xml

This is standard configuration to enable security constraints.

      ACC Security Constraint
      
         Protected Area
         
         /index.zul
	 /secure/*
         
         DELETE
         GET
         POST
         PUT
      
      
         
         Acc User
      
    
	
	  Generic authenticated ACC user
	  Acc User
	
    
    
      FORM
      Form-Based Authentication Area
      
        /login.zul
        /login.zul
      
    
  	
  	120	
  

My JAAS implementation files:

AccLoginModule.java:
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 sharedState;
    private Map 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 sharedState, Map 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;
	}

}
AccPrincipal.java:
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();
	}
}
AccRole.java:
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();
	}
}

Tuesday 21 September 2010

Java Logging & Properties File

I prefer to have the properties 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.

To load the config.properties file at the root of the JAR:

// ResourceBundle expectes the file extension to be .properties by default.
ResourceBundle bundle = ResourceBundle.getBundle("config",
    Locale.getDefault(),
    Thread.currentThread().getContextClassLoader());
String dmsAddress = bundle.getString("dmsAddress");
Similarly when I use Java Logging I put the logging.properties file at the same location and load it upon application start up using LogManager so that I don't have to tamper with the properties file at JRE/lib.

The logging.properties file:

handlers = java.util.logging.ConsoleHandler
.level=INFO

java.util.logging.ConsoleHandler.level = INFO
com.laws.acc.level = ALL
com.laws.acc.ws.level = ALL
It is better to copy the JRE's logging.properties file and modify it. To load the logging configuration:
LogManager logMan=LogManager.getLogManager();
logMan.readConfiguration(Thread.currentThread().getClass().getResourceAsStream("/logging.properties"));

Resources: Smartly Load Your Properties

Monday 16 August 2010

NBN Australia

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.

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 NBN will directly employs tens of thousands of people, 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.

I posted a question on what real benefits average Australians will expect from the NBN 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.

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.

Dick Smith has raised the issue about population and sustainability in Australia. It was hotly debated on Q&A. 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.

Last week, NBN triumphantly announced that the speed of the fibre network can go to 1Gbps – “10 times what they originally envisaged”. 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 the 1Gbps is burst rate, not committed rate (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?

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 Ultra-fast broadband will be slow on overseas links. 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 Zorin OS – 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!

Also, in a wired home environment (or should I say wireless), people typically use WiFi and laptops. With the latest WiFi standard (802.11n), you can expect 40Mbps (and 200Mbps burst). So again, there is a very weak link right at home.

Friday 6 August 2010

Google WAVES Good-Bye

Google promised to change communications with Wave, but instead the service has reached an untimely death.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.

Someone sited top 11 reasons why Wave failed. 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).

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.

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.

Now that the Wave is flushed down the toilet, I wonder what the Google people in Sydney are doing these days.

Sunday 25 July 2010

Virtual Machines

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

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.

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 list of AMD Athlon 64 CPUs 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.

The VM s/w I tried are Oracle/Sun VM VirtualBox 3.2.6, VMWare Player 3.1.0 and VMWare Server 2.0. My understanding is that they are all free.

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.

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.

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.

Now I am happily running Ubuntu 10.04 on VirtualBox and Linux Mint 9 Isadora – both 32-bit of course angel smileys.

Monday 7 June 2010

Eclipse Galileo vs. NetBeans 6.8

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.

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.

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.

Examining the memory usage of both Eclipse and NetBeans with their respective web services client project open, Eclipse occupies about 255M and NetBeans 520M.

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.

Sunday 11 April 2010

Nokia Lost the Plot

Nokia has abandoned the 4-digit naming convention for its mobile phone models and adopted four series of N, E, C, X 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.

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.

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 Nokia’s official maps site 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.

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 many N95 users share this frustration.

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…

Sunday 4 April 2010

Using VoIP on Nokia

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!

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 Lebara and PennyTel. 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.

I like PennyTel for its competitive rates and its usage of standard SIP technology as its VoIP platform. SIP is available on many Nokia phones.  On my N95 it is as simple as following the instructions provided on PennyTel. 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…

Using SIP on XM 5800 is another story. Although the phone does have a place for you to configure SIP settings (Menu -> Settings -> Connectivity -> Admin. Settings -> SIP Settings) but there is no built-in SIP client software. Alas, XM 5800 is not in the table. So I installed Fring on my XM 5800 and the SIP configuration is even simpler – only 3 fields to fill.

On PC (Windows 7) I have tried SIP Communicator and X-Lite. 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 eyeBeam – 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.

Thursday 18 March 2010

Whatever Happened to Programming?

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?

Whatever happened to programming part 1 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. Whatever happened to programming redux is a response to many comments he received so far, with more insightful commentary.

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 my gripe about GAE.

I have said before that the IT industry is modelled after real engineering disciplines like built environment. In built environment, 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.

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

Monday 8 March 2010

Not Happy, GAE!

Cloud Computing has been a latest buzz word recently and seemingly will be for the foreseeable future. I had registered for Google App Engine (GAE) a while ago but did not do anything with it until last week.

I downloaded the Google Plugin for Eclipse 3.5 (Galileo) and installed it without any problem. I then decided to port one of my web applications written in ZK 3.6.2 with Spring Security 3.0.2 in its simplest form.

I started by creating a GAE application in Eclipse using the Google plugin’s wizard. I then copied WebContent directory from my source project to the war directory of the target GAE project. I modified the deployment descriptors according to instructions from ZK small talk with the following additions:

  1. appengine-web.xml – I added /**.xml in the static file exclude path list since in my application I generate .xml files to feed them to the XML/SwfGauge package.
  2. applicationContext-security.xml – 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).

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.

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.  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 WEB-INF/pages 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.

While cloud computing gives you great promises it is also a shackle and limits your freedom of choice . Although GAE seems to be compatible with many frameworks, the degree of compatibility varies. For example, in the list ZK framework is COMPATIBLE 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.  This may be OK for new applications but presents a big obstacle for existing large applications which heavily rely on other frameworks.

Thursday 11 February 2010

Saturday 30 January 2010

VPN Client for Windows 7

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.

Now I am using ShrewSoft VPN Client. 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 online documentation, and its free!

Tuesday 26 January 2010

Flash vs. JavaFX

I wrote the same eCards using JavaFX 1.0 in NetBeans and Flash 10 (with ActionScript 3) in CS4. The contents and algorithms of the eCards are the same but the outcome are quite different.

User Experience

  1. 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.
  2. 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.
  3. 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.

Designer Experience

  1. 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!
  2. 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 nowhere near production quality.
  3. 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.

Related Post:

Friday 22 January 2010

Rise of the Droid

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 hacking LinkedIn API. I don't think many netizens are that interested in LinkedIn, let alone LinkedIn API.

A little digging in my custom reports in Google Analytics revealed that my No. 1 visited blog was Consumer Web Services from Android that I wrote in 2008 when the Android 0.9 SDK Beta was released. For about 12 months, that post was not among the top 5 list. Yet soon after Google announced its launch of Android phone people are are beefed up about development for Android platform.  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.

Kudos to Google!

Friday 15 January 2010

Happy New Year 2010

This is a Flash port of my eCard first written in JavaFX last year.

The snow fall script is stolen from http://r3dux.org/2010/01/actionscript-3-0-particle-systems-2-snow-effect/

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.

Related Post:

Saturday 2 January 2010

Hacking LinkedIn API

Note [2011-03-24]: As promised, I have updated the source code. Now it works! Please see my other post which supercedes this one - Hacking LinkedIn API - Take 2.

Note [2010-03-12]: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.

I was quite excited to discover that LinkedIn was exposing APIs for 3rd-party applications to tap into its data. I downloaded the LinkedIn-J 0.1 SNAPSHOT-2009-12-30 which is a beta java wrapper of the APIs from Google Code and tried the example posted at LinkedIn Developer Network forum. The sample worked quite well as intended.

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.

Once logged in successfully, a PIN is given in the next HTML page:

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.

Looking (view source) at the above login form it is a simple HTML FORM:

...
         
...

Note that the FORM has the following INPUT elements (including the hidden ones and the submit button):

  1. email – use my login to LinkedIn
  2. password – use my password to LinkedIn
  3. duration – set to 0
  4. access – set to –3
  5. agree – set to true
  6. extra – empty
  7. authorize – set to Grant Access

We can easily populate this form and submit it in Java.

...
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&password=mypassword&duration=0&access=-3&agree=true&extra=&authorize=Grant Access");
            dataOut.flush();
            dataOut.close();

            //SSLException thrown here if server certificate is invalid
            String returnedHtml=convertStreamToString(con.getInputStream());
            System.out.println(returnedHtml);
...

If successful, the returnedHTML 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 Beta Java SignPost Sample Code to bypass the LinkedIn login page:

// 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&password=mypassword&duration=0&access=-3&agree=true&extra=&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
        <div class="content">
          You have successfully authorized MyApplication
          Please enter the following security code to enable full access

          <p align="center"><b>12345</b></p>
        </div>
             * 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\"><b>");
            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();
    }
}