Tuesday, March 4, 2003

Applet to Servlet Communication

Abstract

Java™ Applets are limited by the security restrictions they are run under. This is a good thing, mind you, since Applets are applications that are downloaded and run on a person’s computer, sometimes, without them even realizing it. Consequently, Applets must be executed with very tight restrictions. However, this makes developing Applets difficult because resources like databases, XML files, flat files, etc, cannot be accessed. To make Applets more effective, they need to communicate with a 3rd party, a Servlet for example, which has the power to access those resources. The purpose of this paper is to discuss how an Applet can communicate with a Servlet, and in doing so provide much greater functionality than it can alone.

Disclaimer

This post is solely informative. Critically think before using any information presented. Learn from it but ultimately make your own decisions at your own risk.

Hypothetical Project

The goal of this project is to create a Java™ JApplet capable of dragging & dropping database information from one database to another similar database. The JApplet might look something like Figure 1.

Figure 1 - Drag & Drop JApplet

Drag & Drop JApplet
Drag & Drop JApplet

This goal can be accomplished in four broad steps. Each step is a specific area of research and development that must be done in order to be successful in reaching the goal of the project.

  1. Determine what a JApplet is and how to get a simple one running.
  2. Determine how the JApplet will request and receive information from a database.
  3. Determine how drag & drop is provided for in Java™ and how to implement it.
  4. Determine the rules of dragging and dropping so that the application does not try to perform operations that are contrary to the design of the database.

Steps 1 and 2 are going to be covered in this post. Step 3 is covered in a separate post. Step 4 cannot be covered since these are business rules that are specific and unique to each project you work on.

Step 1: What is an Applet?

An Applet is a small Java™ application that extends the java.applet.Applet class or the javax.swing.JApplet class. The compiled *.class files (normally “jarred” up into a *.jar file) sit on the web server along with the rest of the pages of a website. Using the HTML <APPLET> tag on an HTML page will cause the applet’s class files or jar file to be downloaded from the web server onto the client’s machine. After downloading is complete, the Applet is automatically executed within the web browser. Because the Applet is executed within the web browser, it appears to the user as just another element on the web page.

A JApplet is nearly identical to an Applet except for the package that it uses for its GUI components. Applets typically use GUI components from the java.awt package. The GUI components in this package are considered to be a clunky, heavyweight, first-try at implementing GUI components. A much more streamlined, lightweight, and versatile GUI component project was captured in the javax.swing package. All the GUI components of java.awt are re-implemented in javax.swing along with a number of components that do not exist in java.awt at all. The javax.swing package is generally considered to be superior over those in java.awt. Since java.awt does not contain a tree component, javax.swing will have to be used. There is an unwritten rule that GUI components from each of those two packages are not used at the same time in the same application so all GUI components for this project will be from javax.swing.

Web browsers have not caught up with the development of Java™, so special steps must be taken to have a web browser run a JApplet that has swing components. This is the problem: Because the web browser on the client machine executes the JApplet after the JApplet is downloaded, all the classes that the JApplet needs to run have to be found on the client machine. Web browsers typically have the java.awt components build into their installation, but NOT the javax.swing components. The immediate result would be an error from the web browser saying that it could not find a class. To fix the error, a plug-in needs to be downloaded that contains the javax.swing components. To do this the <APPLET> tag in the HTML page needs to be replaced with another tag that will automatically download the plug-in and install it on the client machine (if needed). Replacing the <APPLET> tag is fairly complicated. To simplify the process, a conversion application can be run on the HTML page that will automatically replace the <APPLET> tag. As of this writing the application can be downloaded from http://java.sun.com/products/plugin/index.html, then look for “HTML Converter”. A search of the site for “plugin” or “HTML Converter” should get the right page too if the URL changes in the future. After the converter is downloaded and installed, it can be run on the HTML page that contains the <APPLET> tag(s). The converter will automatically replace them. Opening that HTML page in a web browser again will result in a prompt for downloading and installing the plugin. Once that is done, the web browser can be restarted, the HTML page opened again, and the JApplet will run.

Now, by the end of Step 1, a simple “Hello World” JApplet can be written and executed in a web browser utilizing the plugin that has been downloaded. The first step to completing the goal of the project is complete.

Step 2: Getting data to an Applet

To get data to an Applet, the first instinct is to connect to the database directly using java.sql.Connection. After all, a JApplet is a Java™ application that is running inside a web browser. This is how a connection to a database would be done on any stand-alone application so why not in the JApplet?

Looking into this method, it is quickly discovered that this is not a viable option. There are a number of reasons why.

  1. Following proper Model-View-Controller design, there should already be Data Access Objects (DAOs) that do most of what is already needed. So doing it again in a JApplet is error prone and nothing but reinventing the wheel.
  2. The java.sql package is mostly interfaces and 3rd parties create the working classes. JTurbo (www.jturbo.com) is a popular implementation of these classes for Microsoft SQL Server. Consequently to use this 3rd party code in the JApplet, it would have to be part of the JApplet, and be downloaded with everything else. This would grow the size of the JApplet very quickly since the code implementing the java.sql package would be quite large and complex.
  3. Another problem arising with the code for the java.sql packing being done by a 3rd party is commercial licensing issues. It mostly likely will not be able to be freely distributed and used in this manner.

What will the security manager of a JApplet allow when it comes to making connections (of any kind) outside of the JApplet? Without getting into signed JApplets and certificates, a JApplet is allowed to make connections to the server that it was downloaded from, HTTP request to get a picture or animation to display in the applet for example, and that is it. The security manager blocks all other connections. Knowing this, what method will stay inside the limits of the security manager but ultimately allow access to the database?

To answer this question, leave the world of JApplets for a moment and think about the following scenario. Suppose I have a client (Netscape Navigator), a web server (Apache), a Servlet container (Tomcat), and a database (Microsoft SQL Server). How does information get from the database to the client? Figure 2 shows this network communication.

Figure 2 - Network Communication

Network Communication
Network Communication
  1. A request is made to Apache in a way that Apache will recognize that Tomcat should handle the request. If Tomcat is being used http://jakarta.apache.org/tomcat, the request might be something like http://my.development.server:8080/MyApplication
  2. Apache passes the request to the Tomcat.
  3. Tomcat then contacts Microsoft SQL Server
  4. Microsoft SQL Server returns the desired result sets. Tomcat constructs Java™ objects to hold that information. The information in those objects is then used to create a web page to return in response.
  5. The completed response is then sent to Apache
  6. Apache finally sends the response back to the client machine. The client machine’s web browser recognizes the response as an HTML page and displays it.

The JApplet needs to perform an almost identical process with the difference coming in steps 5 and 6. The JApplet will have GUI components such as trees, scrollbars, and buttons. However, getting a response from the web server in step 6 in the form of an HTML page is useless to the JApplet because the GUI components have no idea what to do with it. A tree is use to work with Java™ objects. If the response in step 6 was in the form of a Collection of Java™ objects then those could be added to a tree to display new nodes very easily. Looking closely at step 4, Java™ objects are constructed and the database information is put in them. Would it not be ideal to return to the JApplet those objects constructed in step 4? But the client machine and the web server are different machines connected only through HTTP requests and responses. Returning a Java™ object does not seem possible.

Java™ API To the Rescue!!

Looking at the java.io package there are two classes that have very suspicious names. ObjectOutputStream and ObjectInputStream. Looking at the descriptions and API of the two it is said that the use of these two is to read and write Java™ objects to and from Streams and to provide methods for doing this simply. What follows in Listing 1–2 is a simple example application that writes a Java™ object to a file and reads it back again.

Listing 1 - A Serializable Object

/** 
 * Cereal.java
 * - A Java object that will be written to a file. Note it implements Serializable
 */
import java.io.*;
public class Cereal implements Serializable 
{
    private String cerealname;    
    
    public Cereal() {}      
    
    public void setCerealName(String name) { cerealname = name; }    
    
    public String toString() {
        StringBuffer sp = new StringBuffer();
        sp.append("[");
        sp.append("cerealname=");
        sp.append(cerealname);
        sp.append("]");
        return sp.toString();
    }
}

Listing 2 - Testing Serialization

/**
 * SerialTest.java
 * - An application that will write a Cereal object to a file and then
 *   read the file back, reconstructing the object that was written
 */
import java.util.*;
import java.io.*;
public class SerialTest {   

    public static void main (String args[]) 
    {        
        SerialTest st = new SerialTest();
        try {
            st.go(args);            
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }

    private void go(String args[])
    throws Exception
    {
        Cereal frostedFlakes = new Cereal();
        frostedFlakes.setCerealName("Banana Frosted Flakes");              
        
        //
        // out to file
        //
        FileOutputStream fos = new FileOutputStream("cereal.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);

        oos.writeObject(frostedFlakes);
        String [] strarray = {"hello", "world", "Michael", "J", "Remijan"};
        oos.writeObject(strarray);
        
        String [] emptyarray = {""};
        oos.writeObject(emptyarray);        
             
        String stringObj = "goodbye world";
        oos.writeUTF(stringObj);
        
        int j = 100;
        oos.writeInt(j);
                     
        oos.flush();
        oos.close();
        fos.close();        
        
        //
        // in from file
        //
        Cereal newFrostedFlakes = new Cereal();
        FileInputStream fis = new FileInputStream("cereal.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        newFrostedFlakes = (Cereal)ois.readObject();
        
        Object [] objarray = (Object [])ois.readObject();
        Object [] emptyObjArray = (Object [])ois.readObject();
        String newStringObj = ois.readUTF();
        int newj = ois.readInt();
        
        ois.close();
        fis.close();
                
        //
        // output what i got from file
        //
        System.out.println(newFrostedFlakes);
        for (int i=0; i<objarray.length; i++)
        {
            System.out.println("("+i+") "+objarray[i]);
        }
        System.out.println("");
        for (int i=0; i<emptyObjArray.length; i++)
        {
            System.out.println("("+i+") "+emptyObjArray[i]);
        }        
        System.out.println(newStringObj);
        System.out.println(newj);
    }
}

BINGO!! This is exactly what is needed for the JApplet. After all, HTTP requests and responses are nothing but streams over a network. As long as the objects used implement the Serializable interface, they can be written to a stream, sent as a response over the web to the JApplet and the JApplet can typecast the object right out of the stream. In addition, since a request is nothing but a stream too, the JApplet can send objects to the web application in order to do more complicated tasks. Figure 3 shows this.

Figure 3 - Serialization Communication

Serialization Communication
Serialization Communication

This is a walk through of how to make a tree in the JApplet fill it in those first nodes with information from the database.

  1. Wrap an ObjectOutputStream around a ByteArrayOutputStream, the JApplet would write any objects to the stream it needs to send to the web application. Using URL and URLConnection objects, a connection is opened to the web server, with request properties:
    • Content-type="application/octet-stream"
    • Content-length="array length of the ByteArrayOutputStream."

A BufferedOutputStream wraps the URLConnection’s output stream and the and the array from the ByteArrayOutputStream is written to the stream.

  1. The web server passes the request to the web application.
  2. The web application wraps an ObjectInputStream around the InputStream from the Request object and uses it to get any Java™ objects out of the stream. Then the web application is free to contact the database if it needs too.
  3. The database returns the desired result. The web application constructs Java™ objects to hold that information. Wrapping an ObjectOutputStream around a ByteArrayOutputStream, those Java™ objects are then written to the stream. Then, using Response.getOutputStream(), a connection is opened back to the JApplet, with request properties
    • Content-type="application/octet-stream"
    • Content-length="array length of the ByteArrayOutputStream."
  4. The stream is then sent to the web server
  5. The response, consisting of a stream of Java™ objects, is then sent back to the JApplet. The JApplet then retrieves the objects from the stream by wrapping the InputStream of the URLConnection in an ObjectInputStream. The JApplet can then typecast the objects appropriately and use them however it needs to.

Now, by the end of Step 2, a fairly complex JApplet can start to be written. A request sent when the JApplet first starts can get those first few initial nodes of the tree from information in the database. Listeners put on the tree can respond to clicks on the nodes by sending additional requests to get children nodes. Buttons added to the JApplet can work with the selected nodes, linking information from one database to another. The needed functionality of the JApplet can now be implemented without going any further, but the convenient drag & drop is still missing. But drag & drop is a topic for another post.

No comments:

Post a Comment