Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Design Patterns, Java, and Web Development


Dr. Dobb's Journal June 1997: Design Patterns, Java, and Web Development

Martin is a consultant and member of the Innovation Center at Raymond James Consulting in Denver, Colorado. He can be reached at [email protected].


Java applets enrich the content of web pages by playing sounds and performing graphics tricks. But there are other applets that enhance web pages in ways that are more useful than lava lamp simulations or bouncing banners. I call these applets "worker" applets, because they labor unseen behind the flashy facades of web pages. In this article, I'll discuss worker applets, provide an example of one, and present three design patterns for incorporating worker applets into web projects.

Worker applets are nonvisual components that fill certain roles (design patterns) in web applications. Worker applets are used as data storage for maintaining the state of a web session when other state-maintenance techniques (such as cookies) are insufficient. For example, if you want to perform operations/calculations on session data, or retrieve it in various sort orders, then Java is usually cleaner and more productive than JavaScript or VBScript. I also use worker applets as "processors" that perform processing and calculations that can be accomplished more efficiently in Java than in a client-side scripting language. For example, in the corporate directory search for one intranet project, I used an applet to parse a search string containing plus and minus symbols as well as Boolean keywords, and translate it into a SQL query. Another important use of worker applets is in data fetching or other network agent roles.

The usefulness of worker applets depends upon one feature available in both Microsoft Internet Explorer 3.0 and Netscape Navigator 3.0. In these browsers, Java applets are callable from client-side scripts. This means that if an HTML page contains a Java applet with public data and methods, then those public members are accessible from client-side scripts on the same or other active HTML pages. Worker applets are usually hidden by setting their dimensions to something like 2×1 pixels (1×1 doesn't work in all browsers). Because these applets perform their functions invisibly, they do not interfere with visually coherent presentations of site content. If your worker applet needs to keep running while the user navigates around the site, you can put your applet in a persistent frame, which can also be hidden if no visible persistent frame exists in your design.

The Invisible Tourist Guide and the Candidate

Figure 1 is a web page that lets users enter keywords ("museum" or "university," for instance) that describe cultural facilities in the fictitious cultural tourist haven of "Yensid." When users click the Query button, the page produces a list of matching facilities, including descriptions and driving directions. Users can request and print out a transcript of the session as a personal record of places to visit in Yensid. The applet lives in the top frame of the page, but is invisible. When initialized by the browser, it fills a hash table with information from a database (in this example, a formatted text file) on the server. For the hash table in this applet, I use the Java Generic Library (JGL) class HashMap, which supports duplicate keys. (The JGL is the Java counterpart of the C++ Standard Template Library, and provides containers and algorithms for Java. It is distributed at no charge by ObjectSpace at http://www.objectspace.com/.) HashMap acts as a simple knowledgebase; once it is loaded, the applet is ready to process queries.

These queries are entered into a standard HTML form's text field in the top frame. There are several JavaScript functions involved in passing the query string to the applet and writing the result out to the lower frame. Listing One hows the HTML and JavaScript for the top frame, including the hidden applet. The JavaScript functions getKnowledge and getHistory are wrappers for the applet's methods of the same name. The JavaScript function respond is the onClick handler for the Query button, and writes the return value of getKnowledge to the lower frame as HTML. The lower frame initially contains a blank page, and subsequently contains only HTML that is dynamically generated by the functions respond and showHistory. If the query result returned by the applet contains HTML tags stored in the knowledgebase, these will be interpreted and the entire result appropriately rendered by the browser in the lower frame. This query-response phase does not require further requests to the server, since the knowledgebase is held by the applet, and the response page is generated dynamically using JavaScript. Even when users request a transcript of a session, the entire transcript is generated by the applet and rendered dynamically by a JavaScript function. (If the dynamically generated HTML contains references to images or other resources that have not yet been cached by the web browser, these alone will be requested from the server.) In this example you can see how HTTP delivery, HTML presentation, and Java can produce a functional solution to an information-delivery problem. Even if the knowledgebase becomes too large to transfer to the client in one piece at initialization time, the applet can be extended to use a caching mechanism to minimize server database accesses during the session.

The applet at the heart of the Tourist Guide web page is the same applet I used in a web page that lets you interview job candidates. It was written as a general (and simple) knowledgebase query engine, and has been reused several times without going back to the source code (see Listing Two). To reuse worker applets, all that is required is a reasonably general implementation of the functionality you need. You just refer to the applet on the page that requires the applet's functionality, and build your HTML around it. Even your JavaScript interface to the applet is largely reusable in other applications.

Why Not Script It?

Can you do this in JavaScript? The answer depends on what exactly you are trying to do, but the question may be misleading. Consider the problem of reuse. It is possible to use client-side scripting to achieve some of the goals I've set for worker applets, but client-side scripting is not an environment that fosters reuse. If you wish to reuse some client-side script, you must either cut-and-paste the code into the current HTML page, or #include the HTML page containing the script you want to use. The first option is simply bad software engineering practice. The second option must be used carefully, because generality is sometimes more difficult to achieve in JavaScript than Java, and #include-ing pieces of script may lead you into a scripting swamp. Applets, on the other hand, are maintained independently of the HTML and JavaScript. They provide better encapsulation than a JavaScript library, and since they are simply "plugged in" to an HTML page as bytecode, they are more likely to encourage proper reuse than snippets of JavaScript. Server-side scripts are expensive to execute, and at least some of the processing done by a traditional CGI or other server-side script can be deferred using worker applets and performed as a single server-side script at the appropriate place in the session. For example, a worker applet could be used to store session data and determine navigation paths in a decision-tree application until the time when the system needs to notify a staff member or log an event. These tasks could then be handled by a server-side script.

Writing Applet-Aware Scripts

Both VBScript and JavaScript can call public methods and operate on public data of Java applets. But VBScript is not natively supported in Netscape's browser, while a close relative of JavaScript (called "JScript") is supported in Microsoft's browser. JavaScript is therefore a portable choice for client-side scripting, and that's why I use it here. And even though both VBScript and JavaScript can make calls to applets, communication from applets to client-side scripts is currently not possible in a portable way. (Navigator implements a mechanism called "LiveConnect," which allows bidirectional communication between applets and script, but this is a proprietary mechanism, and should be used only when your audience is Navigator-only.)

This produces an interesting challenge when calling applets from JavaScript: How do you know when the applet has been started and is ready to call, since the applet cannot tell you directly that it is running? This is a difficult problem that you should be aware of when calling applets of any kind. In general, worker applets can be made to load faster than other applets, because they tend to be smaller due to their limited functional scope and reduced dependence on auxiliary classes -- but if it's not there you can't call it!

The technique I use to circumvent this problem is simple but effective -- I use the functions setAppletsReady and appletsReady and a hidden input field in a form called appletmonitor. The field's value is initially set to 0. I then install the function setAppletsReady (which sets the field's value to 1) as the onLoad handler for the page. Since the onLoad event is not fired until the applets on that page are loaded, setAppletsReady is not called until all applets on that page are loaded. Subsequent calls to appletsReady return the value of the hidden field, indicating whether or not the onLoad event has fired. If appletsReady returns 1, then it is safe to call the applets, otherwise not. The current operation can then block until the applets load, or return an appropriate status message to the user. It may be true that your worker applets are called for the first time only after the user provides some form input, which gives the applet extra time to load. In these cases it may be safe to omit checking whether onLoad has fired. Checking the status of the appletmonitor form, however, is the safer route. It is also possible that you will encounter a browser in which the semantics of the onLoad event don't include waiting for applets. In this case, the technique won't work, and you'll have to find another way of guessing whether the applet is there without actually calling it.

The Data Store

The purpose of the Data Store pattern is to provide an efficient way to store and retrieve state or session data during a web session, such as is commonly tracked in shopping-cart applications. If you want to collect data during a web session, and then perhaps operate on that data and use it to make decisions later in the session, you will be implementing a data store of some kind. The Data Store pattern describes how to build a reusable data storage class, and is applicable when the state data needn't persist between sessions. If the data must persist between browser sessions, you can either use JavaScript cookies or a tool like the Java Persistent Storage Engine (PSE) from Object Design (http://www.odi.com/). The PSE provides for persistent storage of objects on the client machine, and has the advantage over cookies that objects need not be flattened out by the programmer for storage. (PSE is soon to be bundled with Netscape Navigator.)

The benefits of the Data Store pattern are encapsulation of data storage and retrieval functionality (such as returning data based on filters or sort-orders). A liability of a Data Store class (or applet) is the fact that as data storage, it can only persist as long as its containing web page is active. In other words, when the page is gone, the applet is gone; and when the applet is gone, your data is gone. When implementing a Data Store, as in Example 1, consider the following criteria:

  • Do you need between-session persistence? If so, consider cookies instead of an applet.
  • Do you need to retrieve the session data in a special way (sorted, filtered, or otherwise operated on)? You will want to provide auxiliary methods for performing these operations on your data.

Notice in Example 1 that the applet does not provide implementations for most of the standard applet methods, like start() and stop(). The applet does not perform any continuous processing, nor does it need to be stopped by the browser when it is scrolled beyond the current viewport. This Data Store applet implements two custom methods that are minimally required of any Data Store. The put and get methods store and retrieve, respectively, session data in the HashMap. These two custom methods can be overloaded to handle other complex datatypes. Keep in mind, however, that JavaScript takes HTML form field values to be strings, and that some conversion may be necessary. If you want to apply a filter to session data before retrieving it, you could implement one or more methods like the applyFilter method in Example 1 (which includes methods for retrieving the name of the current filter and resetting filtering). Filters would be applied by the get method during subsequent calls, which would return only entries that meet the filter criteria. A sort order could be applied using a similar set of methods. Other implementation decisions about a Data Store include issues like duplicate keys, which can be accommodated by choosing an appropriate data structure.

The Processor

The purpose of the Processor pattern is to perform complex operations/computations on behalf of a web application that are not easily accomplished in JavaScript. JavaScript is not a general-purpose programming language, but an application-scripting language for browsers. There are computations that can be coded and run more efficiently in Java than in JavaScript, and there are probably some operations which are impossible in JavaScript. Also, if you don't want your encryption algorithm, for example, to appear as source code in the user's web browser, you'll want to use a Processor applet to perform your encryption. The benefits of using a Processor class lie in encapsulation, which makes for easy maintenance, and in the advantages of Java over JavaScript. The liabilities of using a Processor class are the same as those of any worker applet: If it hasn't been loaded, you can't use it. A Processor applet has at minimum one custom method; see Example 2. The encryptAndTransfer method can be called by JavaScript to encrypt an element of an entry form, for example, and transfer that element to a processing agent on the server. If the encryption algorithm were public key (which is a good choice for web applications), the key could be passed to the applet as a parameter appearing on the web page. This example is actually a hybrid between a Processor, a pure version of which might perform some numerical processing, and the next pattern -- a Network Agent.

The Network Agent

The purpose of the Network Agent is to handle communications with the web server on behalf of the web application for the purpose of obtaining data from the web server (to update a chat session, for example), or to maintain a bidirectional channel of communications (for example, in a control application with feedback). The Network Agent provides a communications channel to the server distinct from the HTTP requests that the browser makes. If you need a connection to a database (such as the Tourist Guide example would need for a large knowledgebase), you will need a Network Agent unless you want to request a new page from the server, and run a server-side script, for every database query. The benefits of using a Network Agent are, again, encapsulation, as well as the superior socket programming facilities available in Java. A liability of using a Network Agent is that you must usually provide a counterpart of the agent on the server that can provide the necessary services to the client's agent. An implementation of a Network Agent is difficult to generalize, since it may be designed according to several different sub-patterns, according to whether it uses TCP (connection-oriented) or UDP (connectionless) transport, and other criteria. The implementation will usually include one or more request methods and perhaps one or more transfer methods.

Conclusion

Most worker applets are hybrid designs, using elements from several patterns. The knowledgebase applet used in the Tourist Guide application, for example, is both a Data Store, which stores queries for the purpose of generating a transcript, and a Network Agent, which retrieves a knowledgebase from the server. In some cases, it may be more productive to implement the design patterns as stand-alone classes, and to build applets that provide wrappers for one or more methods on an as-needed basis. The applet, then, would be an aggregation of reusable functionality, rather than the original repository of that functionality.

DDJ

Listing One

<html><head>


</p>
<script language="javascript">
    function setAppletsReady()
    {
        document.appletmonitor.loaded.value=1;
    }
    function appletsReady()
    {
        return parseInt(document.appletmonitor.loaded.value);
    }
    function getKnowledge(strQuery, strPrefix, strPostfix)
    {
        if (!appletsReady()) { return null; }
        return document.Guide.getKnowledge(strQuery, strPrefix, strPostfix);
    }
    function getHistory(strFormat, strPrefix, strPostfix)
    {
        if (!appletsReady()) { return null; }
        return document.Guide.getHistory(strFormat, strPrefix, strPostfix);
    }
    function respond()
    {
      if (appletsReady())
      {
        parent.response.document.open();
        parent.response.document.write("<body background=
                                        /remy/backgrounds/stucco.jpg>");
        parent.response.document.write("<table><tr><td width=
                                         100> </td><td>");
        parent.response.document.write(getKnowledge
                      (document.questionform.query.value,"<br>","<br>"));
        parent.response.document.write("</td><td width=
                                         100> </td></tr></table>");
        parent.response.document.write("</body>");
        parent.response.document.close();
      }
    }
    function showHistory()
    {
      if (appletsReady())
      {
        parent.response.document.open();
        parent.response.document.write("<body background=
                                        /remy/backgrounds/stucco.jpg>");
        parent.response.document.write("<table><tr><td width=
                                              100> </td><td>");
        parent.response.document.write("<h2><i>Here is your personalized 
                                              Yensid guide:</i></h2>");
        parent.response.document.write("Here is a transcript of your session 
                             -- you may print this document if you wish");
        parent.response.document.write("<br>");
        parent.response.document.write(getHistory("<hr>Your query: 
           <h3>%q</h3><h3>Produced the answer:</h3><br>%a","<br>","<br>"));
        parent.response.document.write("</td><td width=
                                              100> </td></tr></table>");
        parent.response.document.write("</body>");
        parent.response.document.close();
      }
    }
</script>
</head>
<body background=/remy/backgrounds/stucco.jpg onLoad="setAppletsReady()">
<applet
        code=Candidate.class
    codebase="/remy/java/"
    name=Guide
        width=2
        height=1>
</applet>
<form name=appletmonitor>
    <input type=hidden name=loaded value=0>
</form>


</p>
<center><h4>Enter one or more keywords and click "Query".</h4></center>


</p>
<form name=questionform action="javascript:respond()" method=post>
<center><input type=text name=query size=50></center>
<center>
<table><tr>
<a href="javascript:respond()"><img border=0 src="btnQuery.jpg"></a>
<a href="javascript:showHistory()"><img border=0 src="btnHistory.jpg"></a>
</tr>
</table>
</form>
</body>
</html>

Back to Article

Listing Two

import java.applet.*;import java.util.*;
import java.io.*;
import java.net.*;
import jgl.HashMap;


</p>
public class Candidate extends Applet
{
    private Vector vHistory;
    private HashMap hmapKnowledge;
    public static final String ALWAYS = "always";


</p>
    public void init()
    {
        hmapKnowledge = new HashMap(true); // allow duplicates
        vHistory = new Vector();
        try 
        {
            URL url = new URL(getDocumentBase(),"knowledge.dat");
            DataInputStream din = new DataInputStream(url.openStream());
            String strKeywords = "";
            String strKnowledge = "";
            boolean fStoreMode=false;


</p>
            while (true)
            {
                String buf=din.readLine(); 
                   // break at EOF
                if (buf==null) break;
                   // ignore blank lines
                if (buf.trim().length()<1) continue;
                  // check whether the line is formed as a keyword line
                if ((buf.trim().charAt(0)==':') && 
                                (buf.trim().charAt(buf.length()-1)==':'))
                {
                    if (strKnowledge.length() > 0) 
                    {
                        StringTokenizer tokenizer = 
                                    new StringTokenizer(strKeywords,",");
                        while (tokenizer.hasMoreTokens())
                        {
                            hmapKnowledge.add
                              (tokenizer.nextToken().trim(),strKnowledge);
                        }
                        strKnowledge="";
                    }
                    buf.trim().toLowerCase();
                    strKeywords = buf.substring(1,buf.length()-1);
                    fStoreMode=true;
                }
                else
                {
                    if (fStoreMode)
                    {
                        strKnowledge += buf;
                    }
                }
            }
        } 
        catch (IOException exIO) {} // put some code here
        // catch (MalformedURLException exURL) {} // put some code here
    }
    public String getKnowledge(String strQuery, String strPrefix, 
                                String strPostfix, boolean fAddToHistory)
    {
          // lowercase the query for search against keywords.
        strQuery=strQuery.toLowerCase();
        Enumeration results;
        Vector vKnowledge = new Vector();
                 
        if (fAddToHistory) vHistory.addElement(strQuery);


</p>
        StringTokenizer tokenizer = 
                  new StringTokenizer(strQuery,"'~!@#&();:'\",.<>?/{}[] ");
        while (tokenizer.hasMoreTokens())
        {
            String strWord = tokenizer.nextToken().trim();
            if (strWord.length()<1) continue; // for non-space whitespace
              // first grab relevant items
            results = hmapKnowledge.values(strWord);
            while (results.hasMoreElements())
            {
                String strKnowledgePoint = ((String)results.nextElement());
                if (!vKnowledge.contains(strKnowledgePoint))
                {
                    vKnowledge.addElement(strKnowledgePoint);
                }
            }   
        }
          // now grab the "always" responses
        String strKnowledge = new String();
        results = hmapKnowledge.values(ALWAYS);
        while (results.hasMoreElements())
        {
                String strKnowledgePoint = ((String)results.nextElement());
                if (!vKnowledge.contains(strKnowledgePoint))
                {
                    vKnowledge.addElement(strKnowledgePoint);
                }
        }   
          // now build the string of knowledge
        String strResults = new String();
        results = vKnowledge.elements();


</p>
        while (results.hasMoreElements())
        {
            strResults += strPrefix + results.nextElement() + strPostfix;
        }
        return strResults;
    }
    public String getKnowledge(String strQuery, String strPrefix, 
                                                        String strPostfix)
    {
        return getKnowledge(strQuery, strPrefix, strPostfix, true);
    }
    public String getHistory(String strFormat, String strPrefix,
                                                       String strPostfix)
    {
        String strHistory = "";
        Enumeration queries = vHistory.elements();
        int iQPos = strFormat.indexOf("%q");
        int iAPos = strFormat.indexOf("%a");
        while (queries.hasMoreElements())
        {
            String strQuery = ((String)queries.nextElement());
            strHistory += strFormat.substring(0,iQPos) 
                + strQuery
                + strFormat.substring(iQPos+2,iAPos)
                + getKnowledge(strQuery, strPrefix, strPostfix, false);
                // + strFormat.substring(iAPos+2);
        }
        return strHistory;
    }
}

Back to Article


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.