HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

How enterprise software is written has undergone a major shift with the introduction of distributed technologies like EJBs and Web-based thin clients. However, this new approach to writing software has not trickled down to consumer applications such as recipe managers, cookbooks, or word processors.

I discuss some of the problems that historically have held back distributed development, present an example next-gen application using J2ME, and then discuss some of the architectural issues inherent in distributed consumer software.

Let's face it, there are few real consumer-level distributed applications out there. Traditionally, they're difficult to build. The tools and infrastructure needed to support and deploy wired distributed apps, let alone wireless ones, are hard to come by. CORBA 1.0 was introduced in 1991 and has found a home in enterprise development. Netscape made a valiant attempt to incorporate Visigenic's ORB into their product line, but failed to make CORBA commonplace on the desktop. Microsoft has gone through four component technologies, admittedly not all of them distributed, and is just beginning to make distributed computing a reality on the average user's desktop. Probably the most successful consumer-level distributed applications are the RC5 or SETI clients that are being used around the globe to look for E.T.

However, times have changed. Software developers now have tools that make designing and building distributed applications easier than ever. Wireless technologies and broadband in the home have helped meet the communication requirements. While technologies such as 3G wireless are still a little way out, developers should be planning for the changes that pervasive communications will introduce. There are still some psychological and technical hurdles to overcome, but hopefully the example application will put some of these to rest. Now the question is: "How can a developer leverage the available tools to make an application that's useful for the consumer?"

Users (developers and nondevelopers alike) tend to be task-focused. An application is a tool that gets the job done. The idea behind distributed consumer applications is to provide the user with this tool in a variety of different environments. The key is using the right tool for the right job. For example, a professional carpenter may use a nail gun to frame a house, but a regular hammer to build a tree house. The goal is to nail two pieces of wood together, but the tool varies depending on the environment. Thinking this way about consumer software opens up a whole new type of application development.

A Hands-on Demo
To demonstrate how easy it is to build a distributed application, this article presents a distributed text editor. It's a very simple example of a next-generation consumer application. The editor allows users to create, load, edit, and save text files. More advanced features, such as spell checking and laying out documents, are discussed from an architectural perspective, but not implemented. In the spirit of pervasive computing, users should be able to perform these functions on their data from anywhere using a variety of devices. The sample application will have a Swing-based desktop client and a J2ME-based medium client. All the tools used to develop, test, and deploy this application are freely available and referenced at the end of the article.

Before we get into the technical details of the implementation, let's nail down what we mean by distributed consumer applications:

  1. Clear separation between the client and the "back end"
  2. Transparent support for multiple types of clients
  3. No requirement for a dedicated server
  4. Different clients transparently accessing the same data
By following these principles, we can separate the tool (the client piece) from the task (various "back-end" functions). This follows the typical thin-client enterprise application pattern so far, but items 3 and 4 change things slightly. Our back end should not require a dedicated server, meaning we want to get away from the typical enterprise application server that supports large numbers of users. Instead, we want a lightweight, personal application server that supports a much smaller number of users. Traditional application server capabilities, such as failover or high-end scalability, are not needed for these simpler applications. Finally, the back-end functionality operates on the same set of data regardless of the client being used. A user running the PDA client has access to the same data that was edited with the desktop client.

In the example application, this back-end functionality is provided by EJBs. EJBs make writing the distributed back end easy, however, they're not the ideal solution. Application servers require configuration and are much more comfortable on higher-end machines. A lightweight application server would be a better solution. Failover, clustering, and possibly even transaction support may be removed to help make a lightweight application server more desktop-friendly.

Client technologies are in better shape. Even if you restrict your selection of client platforms to Java, there are several options for scaling from the J2SE desktop client to the J2ME thin client. The sample application uses both to demonstrate transparent data accessibility.

Under the Hood
The application is a typical n-tiered application, in this case, two tiers. (Some would argue three, with persistent storage being the third. However, since we're simply saving information to the local file system with no DAO type pattern, I'm hesitant to call that a tier.) The application uses the file system directly, rather than a database, in keeping with the consumer-level concept. Figure 1 shows the clean separation between the clients and the service-based back end

Figure 1
Figure 1

In a typical enterprise development process we would identify key use cases or user stories. Since we're trying to separate the "tasks" of the application from the GUI, we'll use this approach. To keep things simple, we'll stick with these four key tasks:

  1. Create a new document
  2. Load a document
  3. Edit a document
  4. Save a document
We'll use a stateless session bean, the TextEditorService, to provide the interface to our back end (see Figure 2).

Figure 2
Figure 2

Basic editing of the text document will be the responsibility of the client. Since we'll be accessing the file system directly from the TextEditor, implementing the load and save methods of the bean is easy. The getDocument method is shown in Listing 1.

As mentioned before, we'll provide two different clients, Swing-based and J2ME (see Figures 3 and 4, respectively).

Figure 3
Figure 3

Figure 4
Figure 4

The next architectural issue to be addressed is the communication between the tiers. Even if we were building a Java-only solution, directly accessing the TextEditorService bean would not be a viable option because of our J2ME client. Regardless, since we are designing a services-based application, we should try to stick to standards used in similar applications. We'll use SOAP to communicate with our services layer. SOAP clients are available for nearly every language on just about every platform, including J2ME. On the server side we'll use Apache SOAP to deploy our TextEditorService. Once Apache SOAP is up and running, we can deploy our ser- vice with the SOAP descriptor shown in Listing 2.

For simplicity, we'll be using the Apache SOAP RPCrouter as our servlet interceptor rather than providing our own as suggested by the Sun Blueprints.

For our desktop client we can simply use the client side of Apache SOAP. However, our J2ME client needs a special implementation due to tight memory and performance requirements. Fortunately, a great J2ME SOAP library is available from Enhydra.org, kSOAP. Built on top of kXML, the XML parser and SOAP client together total about 50K. The J2ME client was compiled and tested using Sun's J2ME Wireless Toolkit. Start ktoolbar, create a new project, and simply place the ksoap.jar and kxml-min.jar in the newly created lib directory. Any JARs placed in this directory are automatically added to the classpath when the toolkit compiles your application.

To hide the SOAP interface, we'll create a proxy class that mirrors our TextEditorService interface; however, the method implementations will use SOAP to make the actual calls (see Figure 5 and Listing 3). Note: In our proxy we hard-coded the URL to our SOAP interceptor. This obviously is not the ideal solution and should really be part of the deployment information. However, for the sake of simplicity, it's hard-coded in the source. Our J2ME application will simply make calls to the proxy, unaware that the call is actually being routed to our EJB service.

Figure 5
Figure 5

As seen in Listing 4, our J2ME client creates one form and one text editor widget. The form has space for the user to enter the file name and buttons to retrieve the document. When the document is successfully loaded, the editor is displayed until the user clicks Save or Cancel. Save executes a call to the proxy to store the document using the back end, and Cancel simply redisplays the original form.

The Swing client functions similarly: two JButtons to load and save the document, and a JTextArea to allow the user to edit the document. Again we're using a proxy class to wrap our SOAP interface. kSOAP can be used with J2SE if we use the SE transport, so we could reuse our existing J2ME proxy class (isn't Java wonderful?). However, we'll use the Apache client to demonstrate using a different SOAP implementation.

Once the clients are compiled, the Swing client can be started from the command line. Enter some text in the text area, enter a filename, then click Save. The Swing client then uses the proxy to make a SOAP call to the EJB back end. The TextEditorService will then save the file on the local file system. Now we can start the J2ME client, either through the Wireless Toolkit's emulators, or on an actual J2ME device such as a Palm VII or JavaPhone.

Type in the same file name and click Load. The J2ME client uses the EJB back end to retrieve the same document. While this is obviously a very simple application, it demonstrates the key concepts of distribution, multiple clients, and transparent access to back-end data. However, to some extent this is the easy part.

Architectural Issues
When discussing software architecture, key architectural drivers are often referred to as the "-ilities" - reliability, scalability, etc. Providing a distributed consumer application has some of the same "-ilities" as an enterprise application, plus a few that are somewhat unique to the consumer nature of the application. Below are guidelines to help address these issues and hopefully spur discussion when the solution is not clear.

Security
Security is obviously not unique to consumer applications, however, consumers are not expected to log in to each application before use. To make matters worse, distributed applications inherently make data available to external clients. Securing the communication channel is a must, and, thanks to the XML format used by SOAP, can be easily accomplished with a secure sockets layer (SSL), such as HTTPS to the back end. However, authenticating the user with the back end is more difficult. Ideally the user wouldn't need to be involved in the authentication using a public/private key type system. However, since these applications specifically target devices that can be misplaced or stolen, such as cell phones and PDAs, relying on device authentication is not an effective means of security. Since authentication will be needed for each application, a common framework should be developed. It's possible that projects, such as the Liberty Alliance project, may provide an authentication mechanism that could be used for consumer applications. However, this project is still in the formative stage and a solution is needed immediately.

Designing a security framework for distributed applications is beyond the scope of this article, however, a few key features may be identified:

  1. Single sign-on for applications
  2. Same authentication credentials regardless of client
  3. A fully encapsulated security component that may be easily shared between applications
  4. Support for communication channel encryption
  5. No reliance on the underlying back-end component model
While these requirements are not unique, they need to be met on a wide range of platforms, including limited devices such as cell phones. A quick and dirty solution is to use HTTPS for SOAP communications and make use of the application server's credential support by using cookies to transfer session keys back to the client. J2ME clients can use the CLDC record support to persist this information between invocations. However, since records are not shared between J2ME applications, this is not an ideal solution. J2SE applications fare somewhat better in that a convention can be established to store credential information. As with a typical Web application, credential information will need to expire after a given period of time to avoid the problem of "authenticating the device, not the user."

Task Partitioning
Identifying the physical location of functionality is a key part of architecting a distributed application. The "thin-ness" of the client must be weighed against the ability to use the application without requiring a round-trip to the back end. It's conceptually possible to have thicker clients offering functionality not found on other clients. However, if this functionality is desired on one of the thinner clients, it must be replicated on the back end. A cleaner approach would be to provide as much functionality as possible in the back end and access it from the appropriate clients. Determining client "appropriateness" depends on several factors:

  1. GUI and processing capabilities of the intended device
  2. "Cost" of communicating with the back end, including time, availability, and possible financial costs
  3. Amount of interaction required by the user
  4. Intermediate store requirements placed on the client
By carefully partitioning functionality, it's possible to minimize the interaction and cost to the user. For example, spell checking may be added to our text editor. The spell-checking service may be written so the entire document is checked and the results, identifying which words were not found in the dictionary, are returned to the client. These words could be identified by their position in the document rather than the actual word to reduce the bandwidth required. However, this places the burden on the client to be able to find the identified word. On small memory devices this may not be possible. Once the word is identified, the user may correct it and the corrections are returned to the back end for integration.

Another example of properly partitioning tasks would be needed if we expanded our text editor to support several fonts and styles. Displaying and editing font information would be easy on a Swing-based client; however, it would be nearly impossible on a Java phone. Instead, the back end may provide services to retrieve the content separately from the markup information. Users can use the desktop client to produce their initial document, then make a few quick adjustments with their cell phone. The back end would retain markup information produced by the desktop client even though the cell-phone client can't make use of it.

Hardware Demands
While the example application used EJBs to provide back-end functionality, a lighter weight component model would be more desirable. More specifically, a lightweight application server could make desktop-based distributed applications more of a reality. As mentioned earlier, the scalability and fault tolerance requirements for a desktop application would be much less than that of an enterprise application. However, EJBs do make developing distributed applications relatively easy compared to building CORBA components. In addition to hardware requirements, automated installation and setup would be required. Installing a distributed application shouldn't require any more knowledge on the part of the user than a standard application. However, this places the burden on the application server and the application developer.

It would also be helpful for back-end developers to have a database at their disposal. Again, if properly configured and accessible from the application server, the user interaction would be kept to a minimum.

In all fairness, Microsoft has already provided most of this with their current operating systems, though not for the Java platform. It's time for the Linux distributors to step up to the plate and offer the user a similarly preconfigured set of back-end services.

Integration with Web Services
There has been much talk about Web services and .NET recently. Conceptually they're similar to the services-based, back-end approach outlined here. By leveraging SOAP between your clients and the back end, newly developed consumer applications are poised to integrate with Web services should they become widely available to the consumer.

In the meantime, applications may be developed and deployed without requiring a subscription model or integration with an external system. Desktop clients can be deployed immediately and will appear identical to applications currently available. As wireless devices become more prevalent, more and more application developers can release PDA thin clients to take advantage of previously installed back ends.

Conclusion
Now that broadband connections are becoming commonplace and wireless technology is becoming a reality, developers should begin positioning their applications to leverage the new capabilities. The tools to develop and deploy these new applications are freely available and quite mature. However, end-user support is much further behind.

Acknowledgment
I would like to thank three colleagues for their suggestions, discussions, and reviews: Michael Hudson, Christian Nelson, and Rich Newcomb.

References

  1. JBoss Application Server: www.jboss.org
  2. Ant: http://jakarta.apache.org
  3. Apache SOAP: http://xml.apache.org
  4. kSOAP: http://ksoap.enhydra.org
  5. J2ME Wireless Toolkit: http://java.sun.com
  6. JDK 1.4b3: http://java.sun.com
  7. Netbeans 3.3: www.netbeans.org
Author Bio
Dan Pilone is a software architect for Blueprint Technologies and an active contributor to the open source community. [email protected]

	



Listing 1

/**
 * Returns the text of the document specified in documentName.
 */
public String getDocument(String documentName)
{
  System.out.println("getDocument(" + documentName +")");
  StringBuffer documentText = new StringBuffer();
  
  try
  {
      // We prepend /tmp to prevent creative people
      // from hosing our system...
      BufferedReader inStream = 
        new BufferedReader(new FileReader("/tmp/JDJTemp/" + 								documentName));        
      String tmp = inStream.readLine();
      while (tmp != null)
      {
          documentText.append(tmp);
          tmp = inStream.readLine();
      }
  }
  catch (IOException e) 
  { 
      documentText.append("<Error reading document>"); 
  }
  return documentText.toString();
}



Listing 2

<?xml version="1.0"?>
<isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"
             id="urn:TextEditorService">
  <isd:provider 										type="org.apache.soap.providers.StatelessEJBProvider"
                scope="Application"
                methods="getDocument saveDocument">
    <isd:option key="JNDIName" 
                value="TextEditorServiceHome"/>
    <isd:option key="FullHomeInterfaceName" 
                value="com.pilone.texteditor.TextEditorServiceHome" />
    <isd:option key="ContextProviderURL" 
                value="nova.slac.com:1099" />
    <isd:option key="FullContextFactoryName" 
                value="org.jnp.interfaces.NamingContextFactory" />
  </isd:provider>
  <isd:faultListener>
    org.apache.soap.server.DOMFaultListener
  </isd:faultListener>
</isd:service>



Listing 3

import org.ksoap.*;
import org.ksoap.transport.*;

public class TextEditorServiceProxy
{
  public String getDocument(String documentName)
  {
    String document = null;
    try {
      SoapObject rpc = new SoapObject("urn:TextEditorService", "getDocument");
      rpc.addProperty ("documentName", documentName);
      HttpTransport transport = new HttpTransport("http://www.sys-con.com/soap/servlet/rpcrouter", "");
      document = transport.call(rpc).toString();
    }
    catch (Exception e) { e.printStackTrace(); document = e.toString(); }
    return document;
  }

  public void saveDocument(String documentName, String documentText)
 {
    try {
      SoapObject rpc = new SoapObject("urn:TextEditorService", "saveDocument");
      rpc.addProperty("documentName", documentName);
      rpc.addProperty("documentText", documentText);
      HttpTransport transport = new HttpTransport("http://www.sys-con.com/soap/servlet/rpcrouter", "");
      transport.call(rpc);
    }
    catch (Exception e) { e.printStackTrace(); }
  }
}



Listing 4

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.io.*;
import javax.microedition.io.*;

// SOAP imports...
import org.ksoap.*;
import org.ksoap.transport.*;


public class TextEditorClient extends MIDlet 
  implements CommandListener 
{
    public TextEditorClient () 
    {
	// Create the main form
	mainForm.append(m_DocumentName);
	mainForm.addCommand(m_GetCommand);
	mainForm.addCommand(m_SaveCommand);
	
	// Create the document viewing form
	m_DocumentText.addCommand(m_SaveCommand);
	m_DocumentText.addCommand(m_CancelCommand);
	
	// We'll handle button presses for both
	mainForm.setCommandListener (this);
	m_DocumentText.setCommandListener(this);
    }
    
    public void startApp () 
    {
	Display.getDisplay (this).setCurrent (mainForm);
    }

    public void pauseApp () 
    {
    }

    public void destroyApp (boolean unconditional) 
    {
        // We could persist our current document 
        // to a record store here if needed
    }  

    public void commandAction (Command c, Displayable d) {
	// Find out which command was activated
	if (c == m_GetCommand)
	{
	    // Retrieve the document name, then retrieve the 
        // document from our proxy.
	    String documentName = m_DocumentName.getString (); 
	    m_DocumentText.setString 									(m_TEProxy.getDocument(documentName));
	    Display.getDisplay(this).setCurrent(m_DocumentText);
	}
	else if (c == m_SaveCommand)
	{
        // Ask our proxy to save the object, 
        // then return to the main form
	    m_TEProxy.saveDocument(m_DocumentName.getString(), 
	                           m_DocumentText.getString());
	    Display.getDisplay(this).setCurrent(mainForm);
	}
	else if (c == m_CancelCommand)
	{
	    // Simply redisplay the main form.  We can ignore 
        // changes that were made to the document since 
        // the only way to return to this screen is to
        // try to load a document that will refresh the
        // contents of our document variable
	    Display.getDisplay(this).setCurrent(mainForm);
	}
    }
    
    private Form mainForm = new Form("Text Editor");
    private TextField m_DocumentName = 
      new TextField("Document Name:", "foo", 100, TextField.ANY);
    private TextBox m_DocumentText = 
      new TextBox("", "", 1000, TextField.ANY);
    private Command m_GetCommand = 
      new Command("Load", Command.SCREEN, 1);
    private Command m_SaveCommand = 
      new Command("Save", Command.SCREEN, 1);
    private Command m_CancelCommand = 
      new Command("Cancel", Command.SCREEN, 1); 
    private TextEditorServiceProxy m_TEProxy = 
      new TextEditorServiceProxy();
}

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.