Menu

Developing an OpenLaszlo App

October 18, 2006

Sreekumar Parameswaran Pillai

Introduction

XML technology is in its high tide and companies are enthusiastic to leverage its power and flexibility. Presentation tier technology based on XML is also gaining momentum in this wave. Database vendors have been providing implicit support for XML in their DB products for quite some time. IBM offers XML support with DB2 Express-C software that is free in all aspects: free for development, production, and even distribution. OpenLaszlo has come up with an XML-based scripting framework that is worth notice due to its visual appeal, flexibility, and efficiency. The foundation block is XML and they form a buddy pair unlike any other.

In this article, we shall develop an addressbook application end-to-end using OpenLaszlo, DB2 and Java. The functionality is simple but should be able to demonstrate the integration of technologies discussed above. This is a sequel to Introducing OpenLaszlo, which provided a quick-start on setting up a development environment with Ant and IDE4Laszlo. We will build on this application using the development environment discussed therein.

Download the Required Software

The following software is required in addition to the development environment to try out the address book application:

  1. Download and install DB2 Express-C. This is free for download, development, and production. You can learn more about IBM XML database there too.

  2. Download the source code for the entire application.

Install Software

Install DB2 Express-C by following the instructions in the installation program.

Create the Database adbookdb:

  1. Since this is an XML database, the structure of the table is very simple with just two columns (see Figure 1).

    Diagram of the ADDRESSBOOK table.
    Figure 1. The structure of the adbookdb.

  2. Go to Windows Start >> Programs >> IBM DB2 >> and DB2 Command Editor. Alternatively, you can right-click the DB icon in the windows notification area and start(DB2) or launch Command Editor (see Figure 2).

    DB2 quick launch
    Figure 2. The DB2 Community Edition quick launch

  3. Run the scripts in the following listing to set up the database.

    Listing 1.

    CREATE DATABASE ADBOOKDB USING CODESET UTF-8 TERRITORY US~
    
    
    
    CONNECT TO ADBOOKDB user db2admin using 'db2admin'~
    
    
    
    CREATE SCHEMA DB2ADMIN AUTHORIZATION DB2ADMIN~
    
    
    
    CREATE TABLE DB2ADMIN.ADDRESSBOOK (EMAILID CHARACTER (50) NOT NULL PRIMARY  KEY, CONTACTINFO XML) ~

    The above file contains scripts that will insert 10 records into the database. A person's email ID is the primary key from which the subsequent XML string is inserted into the database. The entire contact information of the person goes as elements of a single XML string. The code below is an example of the insert script: note that to give a one-to-one correspondence to the XML and the primary key, the email ID is maintained as an attribute to the <person> element.

    insert into db2admin.addressbook values ('grace.thomas@yahoo.com','
    
    <person email="grace.thomas@yahoo.com">
    
    <firstname>Grace</firstname>
    
    <lastname>Thomas</lastname>
    
    <phone>9947267690</phone>
    
    <housename>Grace Villa</housename>
    
    <street>III Cross</street>
    
    <city>Pattom</city>
    
    </person>')~
  4. To ensure that data is properly inserted, run the following command from the DB2 Command Editor. DB2 provides excellent views of the XML column in two formats: tree view and source view (see Figures 3 and 4).

    SELECT * FROM DB2ADMIN.ADDRESSBOOK~

    DB2 XML document tree view of the XML field
    Figure 3. Tree view of the XML field in DB2 XML document viewer.

    DB2 XML document source view of the XML field
    Figure 4. Source view of the XML field in DB2 XML document viewer.

Application Flow

The addressbook application is no different from any other application offering the functionality. In fact, it will have only limited features, including:

  1. Adding contact information to the addressbook database
  2. Listing the contacts already in the database
  3. Updating information.

The addressbook application substitutes a simple JSP in place of a full-fledged application. This is possible since DB2 Express-C database supports XQuery functions that can return data in well-formed XML structures. In a production scenario, this JSP would be replaced by an application framework to implement transaction handling and business logic. The application code could be built up using any technology since the presentation layer attains the theoretical 100 percent separation from the application layer and data transfer is achieved using XML.

With OpenLaszlo, presentation needs to be handled differently from the conventional web application since there is no page transition. The data is sent to the web application to be persisted and at the same time the user interface is updated to reflect the changes. However, this poses two challenges:

  1. If the data update is reflected in the GUI in parallel to the application call persisting data, how do we ensure that the data is properly added to the persistence mechanism? What if there is a validation error or an exception that needs to be communicated to the user? One way to solve this would be to refresh the data at the client as soon as a change is made to the database. In this case, the information that is already present at the client will be downloaded once again, which would be inefficient.
  2. Refreshing the entire set of records by fetching it once again could also be a cause for poor response time. We would also miss out on the power of Ajax, which has the power to execute activities asynchronously.

Ultimately, we are left with but one choice: update the client GUI immediately on a confirmation from the application layer. Only the set of changed data would be updated. All the other information is present in the component cache. This way, it would simulate a desktop application in its quick response time. OpenLaszlo's data caching mechanism and the OpenLaszlo API facilitates this function (see Figure 5).

Application flow chart
Figure 5. Flow of the Addressbook application.

Running the Application

  1. Extract the addressbook.zip that comes with this article and copy the files to their locations (see Figure 6).

    Addressbook workspace
    Figure 6. Addressbook application workspace in Eclipse.

  2. Copy the db2jcc.jar and db2jcc_license_cu.jar to the web-inf/lib folder of your web application. These JARs are required for the application to connect to the database. Look in the SQLLIB\java directory under your DB2 installation directory for these JAR files.

  3. Deploy the OpenLaszlo and the Java web application by executing the Ant target deploy-all. This will deploy the OpenLaszlo application to the LPServer and the .war to the default Tomcat 5.0 server.

  4. Start DB2.

  5. Start the OpenLaszlo Presentation Server.

  6. Enter the URL http://localhost:8080/lps-3.2/laszlotutorial/addressbook.lzx in the browser and hit enter. Figure 7 is a screen shot of the main page.

    Addressbook application
    Figure 7. Addressbook application.

Application Functionality

The navigation of the application is intuitive.

  1. Clicking the Add New Contact will lead to a screen to add a person's contact information. The email ID is the only mandatory field. All other information can be updated by returning to the screen anytime (see Figure 8).

    Add new contact
    Figure 8. Adding new contact screen.

  2. Clicking the List all contacts will list all contacts in the database with basic information in a scrollable table (see Figure 9).

    List all contacts
    Figure 9. Listing all the contacts in the database.

  3. Double-clicking the email ID of a person will pop-up the update screen with information currently available in the database (see Figure 10).

    View/update contact
    Figure 10. View or update an existing contact.

  4. Pressing the Update button from this screen will result in an application call to update the record in the database.

  5. Exceptions in the web application layer or any errors in validation can be communicated to the client through a custom XML in the HttpResponse. If you list the contacts in the database with the database stopped, you will get the screen in Figure 11. This could be easily customized to send informative messages to the user.

    Exceptions
    Figure 11. Displaying exceptions to the user.

The following are the .lzx classes that are used in the application:

  1. addressbook.lzx: Declares the canvas on which the entire user interface is built.

  2. contactdetails.lzx: This class generates the screen to show the detailed information of a contact. From here, the user can also update information on a contact.

  3. newcontact.lzx: This is the aggregation of the screen information for a new contact.

  4. datasets.lzx: This class only serves to group the datasets into one file. The file is included in the addressbook.lzx and helps to avoid clutter.

  5. xmlfetcher.jsp: This JSP file represents the application layer in the current setup. In a production scenario, this would be replaced by a framework that would include the business logic and necessary framework to handle transactions, validations etc.

  6. ringingphone.gif: A small graphic to add some spice to the application.

The structure of the GUI is just three views that are hidden initially but made visible when selecting the relevant option. This view is always visible.

Code Walk-through

addressbook.lzx

The include statements compile these two classes into the main file. They have been put in separate .lzx files for the sake of readability.

<include href="contactdetails.lzx" />

<include href="newcontact.lzx" />

<include href="datasets.lzx" />

The onclick() event handler hides the phonelist and the contactallinfo views and brings the Add New Contact screen to visibility.

<text x="10" text="Add a New Contact" fontstyle="bold" bgcolor="#F2F2F2">

<handler name="onclick">

messagetext.setText("");

if(phonelist.visible){phonelist.setVisible(false);}

if(contactallinfo.visible){contactallinfo.setVisible(false);}

newperson.setVisible(true);

newperson.resetForm();

newperson.title.setAttribute('text','New Contact');

</handler>

</text>

The onclick() event handler hides the other views and calls the listAllContacts() method.

<text x="10" text="List all contacts in the book" fontstyle="bold" bgcolor="#F2F2F2">

<method event="onclick">

messagetext.setText("");

if(newperson.visible){newperson.setVisible(false);}

parent.parent.listAllContacts();

contactallinfo.setVisible(false);

phonelist.setVisible(true);

</method>



</text>

The grid component in OpenLaszlo lists the data in an XML path in a table format. The component also allows for sorting from the frontend on any of the columns listed.

<grid id="contactsgrid" width="570" height="140" datapath="listcontactsDS:/addressbook" >

<gridcolumn width="40"  >No

<text x="5" datapath="position()"/>

</gridcolumn>

<gridcolumn width="100"> First Name

<text datapath="firstname/text()"/>

</gridcolumn>

<gridcolumn width="120" sortable="true"> Last Name

<text datapath="lastname/text()"/>

</gridcolumn>

<gridcolumn width="100" sortable="false"> Phone

<text datapath="phone/text()"/>

</gridcolumn>

<gridcolumn width="200" sortable="false"> Email Id

<text datapath="@email" onmouseover="setAttribute('fgcolor',red)"

onmouseout="setAttribute('fgcolor',black)">

The ondblclick() event is handled from the text that shows the email ID of a person. On the double-click event, the getContactdetailsMore() method will be called passing in the text value of this component, which is nothing but the email ID. The following is the process:

  1. The email ID that is the primary key to the contact information is passed to the web application that is currently represented by the JSP.
  2. The JSP returns the detailed information of a contact as a single person node.
  3. The path of the contactallinfo window is set to the new path that holds the detailed information on the above contact. Thus, this window displays the detail information about a particular contact.
<handler name="ondblclick">

phonelist.setVisible(false);

contactallinfo.setVisible(true);

contactallinfo.getContactdetailsMore(this.text);

contactallinfo.setDatapath("contactdetailsDS:/addressbook/person");

contactallinfo.title.setAttribute('text','View/ Update contact details');

</handler>

</text>

</gridcolumn>

</grid>

The listAllContacts() method

  1. Gets the reference to the listcontactsDS from the canvas
  2. Creates an LzParam object, which allows us to add the parameters as name=value pairs as required for an HttpRequest.
  3. The string action=getall is added to the LzParam object, which is associated with the dataset.
  4. The HttpRequest is made by calling the doRequest() method on the LzParam object.

The HttpResponse from xmlfetcher.jsp will populate the listcontactsDS dataset with the entire list of contacts present currently in the database. Also:

  1. The "getall" action returns only four pieces of information pertaining to the contacts in the database: first name, last name, telephone number, and the email address. This information should be sufficient in most cases.
  2. By limiting the amount of data that is fetched during the initial listing, the response time can be made faster.
  3. If the user requires additional information on a particular contact, it can be requested by double-clicking the email ID for the contact.
<method name="listAllContacts" >

var ds=canvas.datasets.listcontactsDS;

var p=new LzParam();

p.addValue("action","getall",true);

ds.setQueryString(p);

ds.doRequest();

</method>

This datapointer points to the result node in the listcontactsDS (dataset) and if there is any change in data of the dataset, the ondata() event will be triggered. If there is an exception or validation error in the application layer, the application (JSP) will put the exception message in this result node of this dataset. At the ondata() event, the datapointer checks for the value of its <result> node; if it is successful, it initiates the GUI update process. If the <result> is a failure, it prints an exception message for the user.

<datapointer xpath="listcontactsDS:/addressbook">

<method event="ondata">

if (this.xpathQuery("listcontactsDS:/addressbook/result[@action='exception']/text()") == "failure") {

messagetext.setText("There is an exception!");

}else{

messagetext.setText("");

}

</method>

</datapointer>

Contactdetails.lzx

The getContactdetailsMore() method gets the entire details on a person based on his email ID. This is loaded to the contactdetailsDS dataset. It is interesting to note the contents of the contactdetailsDS dataset at different stages. Because of the request for update, this dataset will contain detailed information on a contact. When an update is executed, this dataset will have the updated information and as soon as the action is complete, it will hold the exception or success messages. As you will see with the execution of the updateContact method, this dataset will only have information on the status of the update call to the xmlfetcher.jsp.

<method name="getContactdetailsMore" args="email_id " >

var ds=canvas.datasets.contactdetailsDS;

var p=new LzParam();

p.addValue("action","getcontactdetails",true);

p.addValue("emailid",email_id, true);

ds.setQueryString(p);

ds.doRequest();

</method>

The updateContact() method:

  1. Caches the current data from the form fields to class attributes. This is required when the GUI needs to be updated based on the update status.
  2. Persists the new information on a contact to the database.
  3. The dataset contactdetailsDS has the entire details of one contact whose email was passed to the xmlfetcher.jsp.

At different stages, the content of this dataset also keeps on changing. With the execution of the updateContact method, this dataset will only have information on the status of the update call to the xmlfetcher.jsp.

<method name="updateContact">

setAttribute('emailid',hb_1.email_in.getText());

setAttribute('firstnm',hb_2.fname_in.getText());

setAttribute('lastnm',hb_2.lname_in.getText());

setAttribute('phonenmbr',hb_4.pnumber_in.getText());



var ds=canvas.datasets.contactdetailsDS;

var p=new LzParam();

p.addValue("action","updatecontact",true);

p.addValue("emailid",emailid, true);

p.addValue("firstname",firstnm,true);

p.addValue("lastname",lastnm,true);

p.addValue("phonenumber",phonenmbr,true);

p.addValue("housename",hb_3.hname_in.getText(),true);

p.addValue("streetname",hb_3.sname_in.getText(),true);

p.addValue("cityname",hb_4.cname_in.getText(),true);

ds.setQueryString(p);

ds.doRequest();

</method>

newcontact.lzx

The addNewContact() method caches the values from the form fields in local variables for reuse and makes a call to the application layer to update the database.

<method name="addNewContact">

setAttribute('emailid',hb_1.email_in.getText());

setAttribute('firstnm',hb_2.fname_in.getText());

setAttribute('lastnm',hb_2.lname_in.getText());

setAttribute('phonenmbr',hb_4.pnumber_in.getText());



var ds=canvas.datasets.addnewDS;

var p=new LzParam();

p.addValue("action","addnewcontact",true);

p.addValue("emailid",emailid, true);

p.addValue("firstname",firstnm,true);

p.addValue("lastname",lastnm,true);

p.addValue("phonenumber",phonenmbr,true);

p.addValue("housename",hb_3.hname_in.getText(),true);

p.addValue("streetname",hb_3.sname_in.getText(),true);

p.addValue("cityname",hb_4.cname_in.getText(),true);

ds.setQueryString(p);

ds.doRequest();

</method>

Then resets the form components:

<method name="resetForm">

hb_1.email_in.setText("");

hb_2.fname_in.setText("");

hb_2.lname_in.setText("");

hb_4.pnumber_in.setText("");

hb_3.hname_in.setText("");

hb_3.sname_in.setText("");

hb_4.cname_in.setText("");

</method>

The same functionality is provided on other datapointers. Here again, the errors checked are for the add new contact action. If the message is successful, this gets a pointer to the root of the dataset, traverses through the child elements and sets the text to the value it was given in the form components. The datapath of the grid is then updated so that the changed data is visible in the grid.

<datapointer xpath="addnewDS:/addressbook">

<method event="ondata">

if (this.xpathQuery("result[@action='addnewcontact']/text()") == "success") {

messagetext.setText("Contact details successfully Inserted");

var dp = canvas.datasets.listcontactsDS.getPointer();

dp.selectChild(2);

dp.addNodeFromPointer(dp);

dp.setNodeAttribute('email',classroot.emailid);

dp.selectChild();

dp.setNodeText(classroot.firstnm);

dp.selectNext();

dp.setNodeText(classroot.lastnm);

dp.selectNext();

dp.setNodeText(classroot.phonenmbr);

Debug.write('classroot.emailidentifier',classroot.emailid);

parent.setVisible(false);

canvas.mainwindow.listAllContacts();

phonelist.setVisible(true);

contactsgrid.datapath.updateData();

} else if (this.xpathQuery("result[@action='addnewcontact']/text()") == "failure") {

messagetext.setText("Update failed !");

}else if (this.xpathQuery("result[@action='exception']/text()") == "failure") {

messagetext.setText("There is an exception!");

}else{

messagetext.setText("");

}

</method>

</datapointer>

datasets.lzx

This declares all the datasets required for the application

<library>

<!-- dataset for the entire list of contacts in the database -->

<dataset type="http" name="listcontactsDS" src="http://localhost:8080/laszlotutorial/xmlfetcher.jsp?" request="false"/>



<!-- dataset for the entire details of a single contact -->

<dataset type="http" name="contactdetailsDS" src="http://localhost:8080/laszlotutorial/xmlfetcher.jsp?" request="false"/>



<!-- dataset to add a new contact to the database -->

<dataset type="http" name="addnewDS" src="http://localhost:8080/laszlotutorial/xmlfetcher.jsp?" request="false"/>

</library>

xmlfetcher.jsp

The following XQuery returns the email ID, first name, last name, and phone. Note the strings <addressbook> before and after the XQuery expression. This is required to wrap the <person> element within the root element <addressbook>.

if (action.equals("getall")) {

pstmt = connection.prepareStatement("XQuery <addressbook> {

( for $person in db2-fn:xmlcolumn('DB2ADMIN.ADDRESSBOOK.CONTACTINFO')/person"+

"return <person  email=\"{$person/@email}\"> {($person/firstname), ($person/lastname),($person/phone)} </person>)}

</addressbook>");

rs = pstmt.executeQuery();



while (rs.next()) {

System.out.println(rs.getString(1));

out.print(rs.getString(1));

}

}

In this action, the entire information about a person is retrieved and returned to the client. The email ID of the person comes to the JSP as a value of the request parameter--email ID.

if (action.equals("getcontactdetails")) {

String pk = request.getParameter("emailid");



String sql="values(xmlquery(' for $person in db2-fn:xmlcolumn(\"DB2ADMIN.ADDRESSBOOK.CONTACTINFO\")/person"+

" where $person/@email eq $emailid " +

" return <addressbook>{($person)}</addressbook> ' "+

" passing cast(? AS varchar(50) ) as \"emailid\" ))";



pstmt = connection.prepareStatement(sql);



pstmt.setString(1,pk);

rs= pstmt.executeQuery();



while (rs.next()) {

System.out.println(rs.getString(1));

out.print(rs.getString(1));

}

In this action, the new contact information is persisted to the database. The information is parsed from the request parameters and converted into an XML string before it goes into the CONTACTINFO column. In a production environment, this will be taken care of by an XML generator utility within the application framework.



if (action.equals("addnewcontact")) {

String pk = request.getParameter("emailid");

String fname = request.getParameter("firstname");

String lname = request.getParameter("lastname");

String hname = request.getParameter("housename");

String sname = request.getParameter("streetname");

String cname = request.getParameter("cityname");

String phnumber = request.getParameter("phonenumber");



String finalString="<person email="+"\""+ pk +"\""+"><firstname>"+fname+"</firstname><lastname>"+lname+"</lastname><phone>"+

phnumber+"</phone><housename>"+hname+"</housename><street>"+

sname+"</street><city>"+cname+"</city></person>";



String sql="INSERT INTO DB2ADMIN.ADDRESSBOOK(EMAILID ,CONTACTINFO)values('"+pk+"','"+finalString+"')";



stmt = connection.createStatement();

stmt.executeUpdate(sql);



out.print("<addressbook><result action=\"addnewcontact\">success</result></addressbook>");



}

In the updatecontact action, the changed information on a user is updated to the database. As in the earlier case, the XML string is generated and the DB XML column updated with the new string. In this context, we should also consider that the same XML can be produced at the presentation layer and sent as the value of a parameter to achieve the same effect. However, this may not be desirable since in most cases, business logic would need to be executed on the data from the presentation layer before persisting it to the database.

if (action.equals("updatecontact")) {

String pk = request.getParameter("emailid");

String fname = request.getParameter("firstname");

String lname = request.getParameter("lastname");

String hname = request.getParameter("housename");

String sname = request.getParameter("streetname");

String cname = request.getParameter("cityname");

String phnumber = request.getParameter("phonenumber");



String finalString="<person email="+"\""+ pk +"\""+"><firstname>"+fname

+" </firstname><lastname>"+lname+"</lastname><phone>"+

phnumber+"</phone><housename>"+hname+"</housename><street>"+

sname+"</street><city>"+cname+"</city></person>";



String sql="UPDATE DB2ADMIN.ADDRESSBOOK SET CONTACTINFO='"+ finalString+ "' WHERE EMAILID='"+pk +"'";



stmt = connection.createStatement();

stmt.executeUpdate(sql);



out.print("<addressbook><result action=\"updatecontact\">success</result></addressbook>");

System.out.println("<addressbook><result>success</result></addressbook>");



}

The catch block where the exceptions are caught and registered to an XML string and sent to the client. The exception message is available at the <result> </result> node.

} catch (Exception e) {

e.printStackTrace();

out.print("<addressbook><result action=\"exception\">failure</result></addressbook>");

} finally {

System.out.println("executing finally ");

try {

System.out.println("Closing the statement and connection");

if(null!=rs){

rs.close();

}

if(null != pstmt){

pstmt.close();

}

connection.close();

System.out.println("closing the connection");

} catch (Exception e) {

System.out.println("There is exception in the finally block");

e.printStackTrace();

}

}

Conclusion

In this article we explain how to develop an application with DB2 Express-C Edition, OpenLaszlo, and Java technologies. We have explored DB2 XQuery functions to a very limited extent. We have also achieved the ideal 100 percent separation between the view and the application code as expected in a tiered web application, as well as seamless integration between the two using XML. We have also seen a solution to increase system response speed to user actions by manipulating only the changed data in any situation.

References

  1. Software Engineer's Guide to Developing OpenLaszlo Applications
  2. LZX Reference Manual
  3. IBM DB2 Database for Linux, Unix, and Windows information center