Monday, 25 August 2008

The Sorry State of JavaFX

[Update: since JavaFX 1.0 has come out, this article is out-dated. See The Sorry State of JavaFX 1.0 instead.]

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.

JavaFX is fine for its original goal of delivering a 'media' stack for the Java platform. 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.

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 javafx.com 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 Applet Example, when I tried it using javafx-sdk1.0pre1, 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).

Also, no support for being inlineable with HTML will decrease its flexibility as a RIA. Tey Chui gave a comprehensive argument on this issue.

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 javafx.ui.* simply disappeared! This suggests a deeper underlying architectural change.

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 JavaFX Reference documents. However, the current state of these documents are nothing near to be complete or even adequate. For example, the Migration Guide does not tell you what had happed to 'trigger on new' or nullable datatypes (e.g. int?) or recommendations for replacing javafx.ui.* with the scene graph libraries...

For now, I will put my JavaFX exercises on halt and wait for the final v1.0 release.

Friday, 22 August 2008

Consuming Web Services from Android

Earlier this week, Google released Android 0.9 SDK Beta. As usual, I couldn't wait to try it out.

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), Unicode library, JUnit, Apache Commons, ASN.1 libarary and more from Bouncy Castle, kXML, etc. The android.jar file is 11MB!

It is time to write a web service consumer on Android, to consume my WCF web service as well as RESTful service that I developed for a demo.

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 kSOAP 2 to my test project. But I quickly dismissed the idea as my web service built in WCF is not JSR-172 compliant.

Like my exercise in JavaME, I decided to call the RESTful version of the same service built in NetBeans 6.1 instead.

I created a new Android project - SvdemoAndroid, using Eclipse 3.4 with the Android plugin 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 <uses-permission android:name="android.permission.INTERNET" /> to my AndroidManifest.xml file. Now the file looks something like:



    <uses-permission android:name="android.permission.INTERNET" /> 
    
        
            
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            
        
    
 

Android is bundled with Apache HttpClient 4. 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 getKeywords() calls a service returning a plain text on HTTP.

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

The second method calls another service (on URL2). 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 PromoInfo is just a data object holding all the promotion information in its fields.

/**
     * 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<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()>0) {
      return children.item(0).getNodeValue();
     } else
      return null;
    }

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 Microsoft WPF, 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.

I created a style.xml file under the {project}/res/values directory to define the styles for my screen widgets.



    <style name="LabelText">
        18sp
        #fff
        fill_parent 
        wrap_content 
        5px
    </style>
    <style name="ContentText">
        14sp
        #000
        #e81
        true
        
        fill_parent 
        wrap_content 
    </style>

Then my view layout XML can use the styles. Here is the {project}/res/layout/main.xml file:



    <TextView style="@style/LabelText" android:text="Name:"/>
    <TextView style="@style/ContentText" android:id="@+id/tvName" />
    <TextView style="@style/LabelText" android:text="Description:"/>
    <TextView style="@style/ContentText" android:id="@+id/tvDescription" />
    <TextView style="@style/LabelText" android:text="Venue:"/>
    <TextView style="@style/ContentText" android:id="@+id/tvVenue" />
    <TextView style="@style/LabelText" android:text="Date:"/>
    <TextView style="@style/ContentText" android:id="@+id/tvDate" />

Note that by adding the android:text="@+id/tvName", the Android Eclipse Plugin will automatically regenerate the R.java file to add fields into the id class, so that you can reference the widget from Java code using findViewById(R.id.tvName). Here is the code for the onCreate() method:

/** 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());
         }
        }
    }

Running the application:

Android screenshot

Related Posts:

Tuesday, 19 August 2008

Consuming Web Services By JavaME

The J2ME 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 JSR-271 (MIDP 3.0) is finalised Sun has recognised the poor UI capability of JavaME and released the Light Weight UI Toolkit (LWUIT) to stay competitive. Another good 3rd-party framework is J2ME Polish.

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.

I first tried to consume the web services developed using WCF. The Sun's Wireless Toolkit (WTK2.5.2) comes with a utility to generate SOAP client proxy from WSDL. (An alternative is to use KSOAP2). However, JavaME only supports a subset of JAXP and JAX-RPC as defined in JSR-172. My web service data contract contains a DateTime field, which violates JSR-172. Also it is mapped to java.util.Calendar, 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.

Fortunately, I have also developed a RESTful version of the services using NetBeans 6.1 under the project SvdemoRestful. So I created a MIDP 2.0 project named SvdemoWUIT in NetBeans and included the LWUIT JAR in the project.

There are two quick and easy ways to consume RESTful services:

The first way is to use one of NetBeans MIDP wizards to create "Mobile Client to Web Application". 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:

// 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);           
The corresponding stdout shows:
keywords:folk songs, pop music, chinese music ,battle, action, comedy
promo:801Batman The Dark Knight
                        Meet stars in Batman in person - Chritian Bale, Michael Caine.
                Star City2008-7-30 10:00:00Batman, action

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 Utility.java and WebToMobileClient.java by hand to add the missing methods.

The second way is to use the javax.microedition.io.HttpConnection and deal with the low level stuff myself. I needed to parse the Promotion XML and to populate the PromoInfo object. JavaME only offers SAX for XML parsing, so I had to get the InputStream from the HttpConnection and pass it to the SAXParser. SAX uses callback mechanism. Therefore, a XmlHandler class had to be defined.

The MIDlet code snippet:

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();
        }
    }
...

The XmlHandler.java file:

/*
 * 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() > 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);
    }
}

Finally, presenting the retrieved data on the mobile device - the initialiseForm() method in the MIDlet:

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

The following screenshots show the LWUIT 3D cube transition effect and the MIDlet output form respectively.

Related Posts:

Monday, 18 August 2008

Consuming WCF Web Service Using Groovy Client

Last month, I developed some simple web services using WCF and consumed it using various web service frameworks in Java. Now I have implemented the web service client using GroovyWS, which is an add-on module to Groovy. It uses CXF under the hood.

To add the GroovyWS library, I simply copied its JAR file groovy-all-1.5.6.jar into the Groovy's lib directory (it can either go in {groovy.home}/lib or {user.home}/.groovy/lib according to the groovy-starter.conf file). Then run the following script from Groovy Console:

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}"
    }
}
gives the result:
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

That's all!

I then re-did it using Eclipse 3.4 with Groovy Plug-in to create the Groovy project by following the instructions from the same web site. Then I added the JAR files into the dependency:

  • ant.jar from Apache Ant 1.7.0 - this is needed by GroovyWS but not included in its distribution
  • groovy-all-1.5.6.jar
The Groovy code for the test client is very simple:
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}"
   }
  }
 }
}
Compiling then running the program yielded:
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

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 wsdl2java 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).

There are a couple of things I had to do for my code to run without errors:

  1. I had to upgrade my JRE used by Eclipse to a 1.6.0_u10rc. Eclipse automatically addes a bunch of JARs from the JRE, including the resources.jar, 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
  2. The CXF bundled with GroovyWS 0.3.1 had problem generating the code from WSDL - it complained about a namespace "doesnt contain ObjectFactory.class or jaxb.index". It turned out to be a CXF bug and had been fixed in CXF 2.1.1, thanks to a post by Bernie. After I replaced the org/apache/cxf branch of the groovyws-all-0.3.1.jar using the contents in cxf-2.1.1.jar, it was smooth sailing.

Related Posts:

Saturday, 16 August 2008

ORM Frameworks and DBA Friendliness

Database programming used to directly rely on tools provided by the database vendors, such as ESQL-C from Informix, Pro*C 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).

The emergence of Object-Relational Mapping (ORM) 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.

Most ORM or entity frameworks (Hibernate, JPA, Entity Framework) 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.

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.

To have the visibility to the SQL statements up front, there seems to be two options:

  1. resort back to JDBC/ADO.NET... to mix the SQL statements with the business logic code - not advisable
  2. use a framework that exposes the SQL statements - iBATIS SQLMap

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:

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

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.

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.

Tuesday, 12 August 2008

The Life and Death of OC4J

Once upon a time, two smart Swedes wrote a J2EE server called Orion 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.

Then came the 21st century. In 2001, Oracle licensed Orion and created OC4J. 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.

In January 2008, Oracle announced the merger with BEA, 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 Oracle Weblogic Server 10g R3 - 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.

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.

Orion & OC4J

Monday, 11 August 2008

Beijing 2008

The Beijing 2008 Olympic opened with the most spectacular opening ceremony ever. It was China's coming-out party and China spared no effort and expense to impress the world - and indeed it did.

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 Beijing2008.com website.

The website is managed by China's largest information portal - sohu.com. 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.

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.

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

Friday, 8 August 2008

Killer Frameworks vs. Killer Apps

I came across this post 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.

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.

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.

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

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.

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.

Tuesday, 5 August 2008

Why Not Five-teen?

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.

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.

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.

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 fluency). Let's take GUI frameworks as an example.

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:

  1. parent.addChild(child)
  2. child.setParent(parent) or specify the parent in the child's constructor

Most GUI frameworks use the first syntax - such as Swing, Windows Forms, WPF, ASP.NET, GWT, Echo2. For example, in Swing:

JButton button=new JButton("Clear");
button.setActionCommand(command);
button.setToolTipText(tooltip);
button.addActionListener(this);
...
JPanel buttonPanel=new JPanel(new FlowLayout());
buttonPanel.add(button);

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.

To fix this problem, SWT/JFace takes the second approach by using the constructor:

Composite buttons=new Composite(this, SWT.NONE);
...
Button clearButton=new Button(buttons, SWT.PUSH);

This way, you cannot mistakenly set more than one parent for the widget.

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

public abstract class BaseAddressForm extends Form {
...
  void initForm() {
  ...
    add(new Button("clear", new Model("Clear")) {
      @Override
      public void onSubmit() {
        log.info(getModelObject());
        resetModel();
      }
    });
  }
...
}
Similarly, in JavaFX script:
...
bottom: FlowPanel {
  content: [
    Button {
      text: "Clear"
      action: operation() {
        model.selectedAddressIndex=-1;
      }
    },
...

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, GWT-Ext is an add-on to GWT. However, its API naming conventions are not consistent with that of GWT: in GWT the method name getText() is used to get the text label of a widget (e.g. a label, button, etc.); but in GWT-Ext the method is called getValue(). Also, in GWT, the Command is widely used (in a similar way to the Action class in JFace). However, in GWT-Ext event listeners have to be explicitly created...

Don't get me wrong, I love using GWT and GWT-Ext. But these little inconsistencies hinder productivity when using this mix.

Saturday, 2 August 2008

RESTful Web Service

I built a SOAP web service using WCF 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 RESTful web services. I have basicly a couple of options to implement the RESTful web services:

  1. Modify the existing WCF project to support REST style according to MSDN
  2. Create a ASP.NET project and reuse the code that I created in the WCF project.
  3. Create a Java web project and rewrite the code.

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.

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 sample by Doug Seven from where I can steal some code.)

NetBeans (v6.1) has a RESTful web services (JSR-311) plugin using Jersey 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.

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 MVC Framework for ASP.NET. An excellent tutorial and example project using the MVC Framework can be found here.

Since time is of essense in this exercise, I chose option 3.

I started by creating a Java Web Application (called SvdemoRestful) in NetBeans. Then adding a RESTful web service is simply right-clicking the project and choose New -> RESTful Web Services from Patterns... 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.

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

http://hostname/SvdemoRestful/resources/promo/jazz, action

To achieve this, the @Path annotation is defined as

@Path("promo/{keywords}")

I used XPath to retrieve the Promotion record from the data.xml file. I need to make the keyword matching case in-sensitive. In XPath 2.0 there is an upper-case() function, but it does not seem to be supported by the XPath libraries. So I have to use the following XPath statement:

//Promotion[contains(translate(Tags,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'),'keywordsToBeMatched')]

as opposed to

//Promotion[contains(upper-case(Tags),'keywordsToBeMatched')]

So the resulting code for the promo RESTful web service:

/*
 *  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="";
    @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("");
                    NodeList nodes = result.getChildNodes();
                    for(int i=0; i<nodes.getLength(); i++) {
                        Node node = nodes.item(i);
                        if(!"#text".equals(node.getNodeName())){
                            sb.append("<"+node.getNodeName()+">");
                            sb.append(node.getTextContent());
                            sb.append("");
                        }
                    }
                    sb.append("");
                    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) {
    }
}
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 javax.swing.text.html.HTMLEditorKit. 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:
http://hostname/SvdemoRestful/resources/webPageKeywords/http://localhost/someWebPage.html
http://hostname/SvdemoRestful/resources/webPageKeywords/'http://localhost/someWebPage.html'
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 limited and encode attributes of the @Path(), but to no avail. So I had to resort to passing the parameters as the URL's query parameter:
http://hostname/SvdemoRestful/resources/webPageKeywords?url=http://localhost/someWebPage.html
The corresponding @Path annotation is just @Path("webPageKeywords"). The corresponding service implementation code snippet:
/**
 * 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) {
    }
}
I am looking forward to the new ASP.NET MVC Framework and will definitely rewrite these services using it.