Personal tools

Laszlosoapapp

From OpenLaszlo

Contents

OpenLaszlo and Java SOAP Interoperability

This sample OpenLaszlo application obtains a list of Bugs (software defects) from a Java POJO Bug Tracking Service and displays them as a grid (OpenLaszlo table). This scenario explains how to set up the Laszlo application as well as how to expose the Bug Tracking Service as a web service that Laszlo can use. You will need a basic knowledge of Java, Java Servlets, XML and the Laszlo Presentation Server in order to complete this example.

My Setup

   * Sun JDK 5.0
   * Jetty HTTP server
   * Open Laszlo 3.0.2
   * XFire SOAP Framework (1.0 development snapshot as of 8/13/2005)

Key Learnings

  1. Don't use Axis for document style web services. It appears not to be compliant with the WS-I draft spec, and it does not work with Laszlo.
  2. Do use Xfire-it's simple and it works with Laszlo, plus it's from the Codehaus group of projects, which is filling in many portions of the enterprise stack and tends to adhere to the kind of lightweight approach I like.
  3. Don't rely on the dataobject parameter of the remotecall Laszlo tag, as you'll end up with extraneous XML.
  4. Do use Javascript to bind responses from your remotecall to a dataset
  5. Use the wrapped style for your XFire service binding

XFire Step-by-Step

Install a Servlet Container (or use the one that comes with Laszlo)

Download and install XFire into your servlet container. My web.xml for XFire looks as follows:


  <?xml version="1.0" encoding="ISO-8859-1"?>
  <web-app 
    xmlns="http://java.sun.com/xml/ns/j2ee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation=
        "http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" 
    version="2.4"> 

        <display-name>BugServe</display-name>
        <description/>

        <servlet>
                <servlet-name>XFire</servlet-name>
                <servlet-class>
                        org.codehaus.xfire.transport.http.XFireConfigurableServlet
                </servlet-class>
        </servlet>

        <servlet-mapping>
                <servlet-name>XFire</servlet-name>
                <url-pattern>/servlet/XFireServlet/*</url-pattern>
        </servlet-mapping>

        <servlet-mapping>
                <servlet-name>XFire</servlet-name>
                <url-pattern>/services/*</url-pattern>
        </servlet-mapping>
  </web-app>

Set up your org.bugserve.Bug JavaBean, adhering to the following interface:

  public interface Bug
  {
        public abstract String getDescription();
        public abstract void setDescription(String description);
        public abstract Long getId();
        public abstract void setId(Long id);
        public abstract String getNotes();
        public abstract void setNotes(String notes);
  }
  • You can download this zip file to get the full source code and configuration for this example (as an Eclipse 3.1 project).

Set up your org.bugserve.BugService. This POJO service adheres to the below interface:

  public interface BugService
  {
        public Bug[] getAllBugs();
        public Collection<Bug> getAllBugsAsCollection();
  }
  • Note that both the array style return as well as the Collection return work with Laszlo and XFire.

Set up your XFire 'services.xml':

        <xfire>
          <services>
            <service>
              <name>BugService</name>
              <namespace>urn:bugserve:BugService</namespace>
              <style>wrapped</style>
              <soapVersion>1.2</soapVersion>
              <serviceClass>org.bugserve.BugService</serviceClass>
              <implementationClass>org.bugserve.BugServiceImpl</implementationClass>
            </service>
          </services>
        </xfire>
  • This file needs to go into the location META-INF/xfire/services.xml, relative to the Classpath.
  • Note the use of wrapped style--this is critical for interoperating with Laszlo in this example!
  • I'm using SOAP version 1.2, but 1.1 should work as well
  • Specifying a separate serviceClass and implementationClass allows you to provide an interface that strictly defines what methods will be available externally (in other words, only the public methods on BugService are exposed, and any additional public methods on BugServiceImpl will not be made available via the web service).

That's it!

Laszlo Step-by-Step

Download and install OpenLaszlo

Create a file in my-apps that looks as follows:

        <canvas width="800" height="600">
            <dataset name="bugs" />

            <soap name="bugService" 
                wsdl="http://localhost:9080/services/BugService?wsdl">

                <method event="ondata" args="value">
                        canvas.bugs.setChildNodes(value[0].childNodes);
                        Debug.inspect(canvas.bugs);
                </method>

                <remotecall funcname="getAllBugsAsCollection">
                        <param value="${parent.makedoc(parent.funcname)}" />

                        <method name="makedoc" args="func">
                                <![CDATA[
                                        return '<' + func + ' xmlns="' + 'urn:bugserve:BugService' + '" />';
                                ]]>
                        </method>
                </remotecall>
            </soap>

            <button text="Click Here to Get Bugs">
                <method event="onClick">
                        canvas.bugService.getAllBugsAsCollection.invoke();
                </method>
            </button>

            <grid y="30" datapath="bugs:/*" width="700" height="150">
                        <gridtext name="id" datapath="id/text()" ascendsort="true">ID</gridtext>
                        <gridtext datapath="description/text()">Description</gridtext>
                        <gridtext datapath="notes/text()">Notes</gridtext>
            </grid>

            <debug x="250" y="150" width="500" height="400" />
        </canvas>

Let's take a look at the interesting pieces of this OpenLaszlo app.

Declare an empty dataset :

            <dataset name="bugs" />

Declare a soap call:

            <soap name="bugService" 
                wsdl="http://localhost:9080/services/BugService?wsdl">
  • Make sure to update the WSDL URL as appropriate for your environment

Declare an event handler method for the ondata event of the soap call:

                <method event="ondata" args="value">
                        canvas.bugs.setChildNodes(value[0].childNodes);
                        Debug.inspect(canvas.bugs);
                </method>
  • Document style return values come back in an array (usually with just one element). That's the case here. We take that one element, and set its childNodes as the childNodes of the dataset.
  • The Debug.inspect call will display the dataset to the debugger, where you can check that the return XML was mapped appropriately.

Declare a remotecall corresponding to the getAllBugsAsCollection() method on 'org.bugserve.BugService':

                <remotecall funcname="getAllBugsAsCollection">
                        <param value="${parent.makedoc(parent.funcname)}" />

                        <method name="makedoc" args="func">
                                <![CDATA[
                                        return '<' + func + ' xmlns="' + 'urn:bugserve:BugService' + '" />';
                                ]]>
                        </method>
                </remotecall>
  • The funcname is the exact name of the SOAP operation (which happens to be the exact name of the method on the Java service class)
  • The param declares an input parameter for the SOAP call. Because we are using a document literal/wrapped service with XFire, there is always one, and only one, input parameter. This input parameter, like the output parameter, is a document. For some reason, if you specify a document style service instead of a wrapped style service in your XFire service.xml, XFire will not accept an input parameter for this method. However, it still attempts to read the input parameter, causing an exception/fault.
  • The method makedoc encapsulates the logic for constructing an input message. The getAllBugsAsCollection method on the Java service has no input parameters, however since we are using document style SOAP, the input message needs to contain the operation name. In this case, the message ends up being <getAllBugsAsCollection xmlns="urn:bugserve:BugService" />. This logic could be abstracted into a specialized sub-class of remotecall, as it will need to be used anytime you do document style SOAP from Laszlo to XFire.
  • Note the use of CDATA around the scripting content to avoid XML parsing issues (generally a good idea on any embedded script).

Create a button that invokes the web service method using the 'remotecall':

            <button text="Click Here to Get Bugs">
                <method event="onClick">
                        canvas.bugService.getAllBugsAsCollection.invoke();
                </method>
            </button>
  • You can reference the remotecall by its funcname
  • Use the invoke() method to kick off the remotecall
  • Remember that all OpenLaszlo RPC calls are asynchronous, so don't expect an immediate result

Create a grid control to display the results:

            <grid y="30" datapath="bugs:/*" width="700" height="150">
                        <gridtext name="id" datapath="id/text()" ascendsort="true">ID</gridtext>
                        <gridtext datapath="description/text()">Description</gridtext>
                        <gridtext datapath="notes/text()">Notes</gridtext>
            </grid>
  • bugs:/* will select all <Bug> nodes from the return value
  • Note how the grid automatically becomes visible once the remotecall has finished and the dataset has been populated

That's it. To run the example, just make sure that both Laszlo and your web service server are up, go to the OpenLaszlo application in your browser and click on the button. Some fun next steps will be to add data submission and error handling capabilities.