Book Home Java Servlet Programming Search this book

5.4. HTML Generation

No, "HTML Generation" is not another name for the children born in the 1980s, many of whom grew up browsing the web--although Jason and Will, saddled with the Generation X moniker, feel that would be only fair. HTML generation is an alternate way for servlets to send HTML content to clients.

So far, every example in this book has generated its HTML by hand, as one long String that is sent to the client. This strategy works fine for small web pages (like book examples), but it quickly becomes unwieldy for larger, more complicated pages. For that type of page, it's sometimes helpful to use an HTML generation package.

An HTML generation package provides a servlet with a set of classes that abstract away the details of HTML, in particular, the HTML tags. The level of abstraction depends on the package: some put only the thinnest veneer above the HTML tags, leaving the nitty-gritty details (such as opening and closing each HTML tag) to the programmer. Using packages such as these is similar to writing HTML by hand and is not discussed here. Other packages elegantly abstract away the HTML specification and treat HTML as just another set of Java objects. A web page is seen as an object that can contain other HTML objects (such as lists and tables) that can contain yet more HTML objects (such as list items and table cells). This object-oriented approach can greatly simplify the task of generating HTML and make a servlet easier to write, easier to maintain, and sometimes even more efficient.

5.4.1. Generating Hello World

Let's look at an example to see how object-oriented HTML generation works. Example 5-3 shows the ubiquitous HelloWorld servlet, rewritten to take advantage of WebLogic's htmlKona package (available for free evaluation and purchase at http://www.weblogic.com--you may need to poke around a bit to find it).

Example 5-3. Hello, htmlKona

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

import weblogic.html.*;

public class HtmlKonaHello extends HttpServlet {

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {

    res.setContentType("text/html");

    ServletPage page = new ServletPage();
    page.getHead().addElement(new TitleElement("Hello World"));
    page.getBody().addElement(new BigElement("Hello World!"));

    page.output(res.getOutputStream());
  }
}

Note how all the HTML tags have been replaced with objects. This servlet first creates a new ServletPage object that represents the web page it will return. Then, it adds a "Hello World" title to the page's head section and a "Hello World!" big string to its body section. Finally, the servlet outputs the page to its output stream.[2] That's how object-oriented HTML generation works: get a page object, add component objects to it, and send it to the output stream.

[2] We must use the ServletOutputStream here since htmlKona was not written to output its page to a PrintWriter.

One advantage of HTML generation should already be apparent: it ensures valid HTML. HTML generation eliminates the possibility for a misspelled <TITLE> open tag or a forgotten </TITLE> close tag. We'll admit it's not an advantage worth writing home about, but it is appealing to not have to remember to open and close every tag or to clutter your code with HTML. Unfortunately, object-oriented HTML has the fairly serious drawback that it can litter memory with a multitude of small objects, requiring more frequent garbage collection.

5.4.2. Generating a Weather Forecast

That's how HTML generation works for a simple web page. Now let's create a more complicated web page, so we can test how HTML generation scales to handle the harder challenges. Figure 5-1 shows a hypothetical web page that displays the current weather and an extended forecast, the kind you might find on Yahoo! or CNN. We've kept it simple for the sake of space, but it still includes enough components to make an interesting example.

figure

Figure 5-1. Oh, the weather outside is delightful

Imagine a servlet creating this web page. Assuming the servlet already has access to the current conditions and forecast information, how would the servlet do it? We will examine and discuss three strategies:

The first strategy, constructing the HTML by hand (Example 5-4), is the standard approach demonstrated elsewhere in this book. A servlet implemented using this strategy acts as a baseline against which we can compare the other two servlets. The second approach, using an HTML generator (Example 5-5), constructs the web page as a set of objects. This is like the HelloWorld example, just on a much larger scale. The third strategy, using an HTML generator and some creativity (Example 5-6), takes the second servlet and simplifies it by reusing objects and subclassing.

5.4.2.1. Weather forecast constructed by hand

Example 5-4 shows a servlet that creates the weather forecast page without using HTML generation, manually sending its content wrapped with almost a hundred HTML tags.

Example 5-4. Weather forecast constructed by hand

import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class WeatherHtml extends HttpServlet {

  // Some static final variables to populate the page...
  // These would normally come from a database or 
  // maybe another servlet that retrieved it as POST data.

  static final int currentTemp = 70;
  static final String currentImage = "/images/rainy.gif";
  static final String[] forecastDay = { "Thursday",
                                        "Friday",
                                        "Saturday" };
  static final String[] forecastImage = { "/images/sunny.gif", 
                                          "/images/sunny.gif",
                                          "/images/rainy.gif" };
  static final int[] forecastHi = { 82, 82, 73 };
  static final int[] forecastLo = { 58, 65, 48 };

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    // Set its title
    String title = "Sebastopol Weather Forecast";
    out.println("<HTML>");
    out.println("<HEAD>");
    out.println("<TITLE>" + title + "</TITLE>");
    out.println("</HEAD>");

    // Start on the body
    out.println("<BODY>");

    // Make a centered table
    out.println("<CENTER>");
    out.println("<TABLE BORDER=1 CELLPADDING=0 CELLSPACING=0 WIDTH=70%>");

    // First row
    out.println("<TR>");
    out.println("<TD><CENTER><B>");
    out.println("<FONT SIZE=+2>Current Conditions</FONT>");
    out.println("</B></CENTER></TD>");

    out.println("<TD><CENTEr>");
    out.println("<IMG WIDTH=48 HEIGHT=35 SRC=\"" + currentImage + "\">");
    out.println("</CENTER></TD>");

    out.println("<TD COLSPAN=2><CENTER><B><FONT SIZE=+2>");
    out.println(currentTemp + "&#176;");
    out.println("</FONT></B></CENTER></TD>");
    out.println("</TR>");

    // Second row
    out.println("<TR>");
    out.println("<TD COLSPAN=2><CENTER><B><FONT SIZE=+1>");
    out.println("Extended Forecast");
    out.println("</FONT></B></CENTER></TD>");

    out.println("<TD><CENTER><B><FONT SIZE=+1>");
    out.println("Hi");
    out.println("</FONT></B></CENTER></TD>");

    out.println("<TD><CENTER><B><FONT SIZE=+1>");
    out.println("Lo");
    out.println("</FONT></B></CENTER></TD>");
    out.println("</TR>");

    // Daily forecast rows
    for (int i = 0; i < forecastDay.length; i++) {
      out.println("<TR>");
      out.println("<TD>&nbsp;<FONT SIZE=+1>");
      out.println(forecastDay[i]);
      out.println("</FONT></TD>");
      out.println("<TD><CENTER>");
      out.println("<IMG WIDTH=48 HEIGHT=35 SRC=\"" + forecastImage[i] + "\">");
      out.println("</CENTER></TD>");
      out.println("<TD><CENTER><FONT SIZE=+1>");
      out.println(forecastHi[i]);
      out.println("</FONT></CENTER></TD>");
      out.println("<TD><CENTER><FONT SIZE=+1>");
      out.println(forecastLo[i]);
      out.println("</FONT></CENTER></TD>");
      out.println("</TR>");
    }

    // Close the still-open tags
    out.println("</TABLE>");
    out.println("</CENTER>");
    out.println("</BODY></HTML>");
  }
}

This code exactly generates the weather forecast page as shown in Figure 5-1. It begins by defining static final variables to use as its content and proceeds to nest that content among HTML tags. This approach presents a pretty page to the end user, but it can leave the programmer counting tags and looking for the right place to put the forgotten </TD>. The approach also has limited maintainability. Pulling out one HTML tag can result in the same cascading disaster you get when you pull on a knit sweater's loose tail. And for the same reason--everything's connected. Even a change as simple as decentering the table requires a modification in the beginning of doGet() and at the end. And a whimsical change, like making the extended forecast font bold, requires more than a little concentration.

5.4.2.2. Weather forecast using HTML generation

The same servlet written using HTML generation is shown in Example 5-5.

Example 5-5. Weather forecast using HTML generation

import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import weblogic.html.*;

public class WeatherHtmlKona extends HttpServlet {

  // Some static final variables to populate the page...
  // These would normally come from a database or 
  // maybe another servlet that retrieved it as POST data.

  static final int currentTemp = 70;
  static final String currentImage = "/images/rainy.gif";
  static final String[] forecastDay = { "Thursday",
                                        "Friday",
                                        "Saturday" };
  static final String[] forecastImage = { "/images/sunny.gif", 
                                          "/images/sunny.gif",
                                          "/images/rainy.gif" };
  static final int[] forecastHi = { 82, 82, 73 };
  static final int[] forecastLo = { 58, 65, 48 };

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/html");

    // Get a new page
    ServletPage page = new ServletPage();
    
    // Set its title
    String title = "Sebastopol Weather Forecast";
    page.getHead().addElement(new TitleElement(title));

    // Get the body
    HtmlContainer body = page.getBody();

    // Make a table, and add it to the body (even before it's filled)
    TableElement tab = new TableElement()
                       .setCellPadding(0)
                       .setCellSpacing(0)
                       .setBorder(1)
                       .setWidth("60%");
    body.addElement(new CenteredElement(tab));

    // Create the first row
    HtmlElement conditions = new StringElement("Current Conditions")
                             .asFontElement("+2")
                             .asBoldElement()
                             .asCenteredElement();
    HtmlElement image = new ImageElement(currentImage)
                        .setWidth(48)
                        .setHeight(35)
                        .asCenteredElement();
    HtmlElement temp = new StringElement(currentTemp + "&#176;")
                       .asFontElement("+2")
                       .asBoldElement()
                       .asCenteredElement();
    tab.addElement(new TableRowElement()
                   .addElement(new TableDataElement(conditions))
                   .addElement(new TableDataElement(image))
                   .addElement(new TableDataElement(temp)
                               .setColSpan(2)));

    // Create the second row
    HtmlElement extended = new StringElement("Extended Forecast")
                           .asFontElement("+1")
                           .asBoldElement()
                           .asCenteredElement();
    HtmlElement hi = new StringElement("Hi")
                     .asFontElement("+1")
                     .asBoldElement()
                     .asCenteredElement();
    HtmlElement lo = new StringElement("Lo")
                     .asFontElement("+1")
                     .asBoldElement()
                     .asCenteredElement();
    tab.addElement(new TableRowElement()
                   .addElement(new TableDataElement(extended)
                               .setColSpan(2))
                   .addElement(new TableDataElement(hi))
                   .addElement(new TableDataElement(lo)));

    // Create the forecast rows
    for (int i = 0; i < forecastDay.length; i++) {
      HtmlElement day = new StringElement("&nbsp;" + forecastDay[i])
                        .asFontElement("+1");
      HtmlElement daypic = new ImageElement(forecastImage[i])
                           .setWidth(48)
                           .setHeight(35)
                           .asCenteredElement();
      HtmlElement dayhi = new StringElement("" + forecastHi[i])
                          .asFontElement("+1")
                          .asCenteredElement();
      HtmlElement daylo = new StringElement("" + forecastLo[i])
                          .asFontElement("+1")
                          .asCenteredElement();
      tab.addElement(new TableRowElement()
                     .addElement(new TableDataElement(day))
                     .addElement(new TableDataElement(daypic))
                     .addElement(new TableDataElement(dayhi))
                     .addElement(new TableDataElement(daylo)));
    }

    // Send the page to the response's output stream
    page.output(res.getOutputStream());
  }
}

The basic structure of this servlet is similar to that of the previous example. The major difference is that this servlet uses an HTML generation package to create an object-oriented representation of the web page.

A few things may look strange about this code. The most striking is its use of method chaining, where several methods are invoked on the same object with code like the following:

TableElement tab = new TableElement()
                   .setCellPadding(0)
                   .setCellSpacing(0);

The whitespace here is irrelevant. The previous code is equivalent to:

TableElement tab = new TableElement().setCellPadding(0).setCellSpacing(0);

This chaining is possible because each "set" method returns a reference to the object on which it was invoked--that reference is used to invoke the next "set" method. This trick comes in handy when using htmlKona.

You may also be wondering why so many objects are declared as HtmlElement objects but created as StringElement objects or ImageElement objects, as with the following code:

HtmlElement image = new ImageElement(currentImage)
                    .setWidth(48)
                    .setHeight(35)
                    .asCenteredElement();

The answer is that each "as" method returns an object of a different type than the object on which it was invoked. In the example above, the asCenteredElement() method returns a CenteredElement wrapped around the original ImageElement. For simplicity, each HTML component can be declared to be of type HtmlElement, which is the superclass of all HTML objects--its actual subclass type can be changed later with ease.

Now let's look at how this servlet compares to the previous servlet. This servlet no longer has code that writes the individual HTML tags, but it replaces that code with almost as many method invocations. We don't appear to be saving any keystrokes. What using HTML generation does do is give you confidence that the page you constructed is valid. Tags cannot be forgotten or misplaced. The larger benefit comes from easier maintainability. What if your pointy-haired boss wants the table left-justified instead of centered? The change is simple. The following line:

body.addElement(new CenteredElement(tab));

changes to:

body.addElement(tab);

And what if you decide you want the forecast font to be bold? Well, it's still a lot of work. For an elegant solution to this problem, we need to look at the next servlet.

5.4.2.3. Weather forecast using HTML generation creatively

Example 5-6 (the last full weather forecast example) shows another servlet that generates the weather forecast web page. This servlet demonstrates some of HTML generation's potential by reusing objects and subclassing. This technique produces results similar to what you can achieve with Cascading Style Sheets (CSS), a recent enhancement to HTML for controlling document appearance.[3] The major advantage of HTML generation is that, because it operates entirely on the server side, it can work with all browsers. CSS only started being supported in Microsoft Internet Explorer 3 and later and Netscape Navigator 4 and later.

[3] For more information on Cascading Style Sheets, see http://www.w3.org/Style/css.

Example 5-6. Weather forecast using HTML generation creatively

import java.io.*;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import weblogic.html.*;

class CurrentStyle extends StringElement {
  CurrentStyle(String val) {
    super(new StringElement(val)
          .asFontElement("+2")
          .asBoldElement()
          .asCenteredElement());
  }
}

class ExtendedTitleStyle extends StringElement {
  ExtendedTitleStyle(String val) {
    super(new StringElement(val)
          .asFontElement("+1")
          .asBoldElement()
          .asCenteredElement());
  }
}

class ExtendedDayStyle extends StringElement {
  ExtendedDayStyle(String val) {
    super(new StringElement(val)
          .asFontElement("+1"));
  }
}

class ExtendedTempStyle extends StringElement {
  ExtendedTempStyle(String val) {
    super(new StringElement(val)
          .asFontElement("+1")
          .asCenteredElement());
  }
}

class ImageStyle extends CenteredElement {
  ImageStyle(String src) {
    super(new ImageElement(src).setWidth(48).setHeight(35));
  }
}

public class WeatherHtmlKonaRevised extends HttpServlet {

  static final ImageStyle sunny = new ImageStyle("/images/sunny.gif");
  static final ImageStyle rainy = new ImageStyle("/images/rainy.gif");

  // Some static final variables to populate the page...
  // These would normally come from a database or 
  // maybe another servlet that retrieved it as POST data.

  static final int currentTemp = 70;
  static final ImageStyle currentImage = sunny;
  static final String[] forecastDay = { "Thursday", "Friday", "Saturday" };
  static final ImageStyle[] forecastImage = { sunny, sunny, rainy };
  static final int[] forecastHi = { 82, 82, 73 };
  static final int[] forecastLo = { 58, 65, 48 };

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/html");

    // Get a new page
    ServletPage page = new ServletPage();
    
    // Set its title
    String title = "Sebastopol Weather Forecast";
    page.getHead().addElement(new TitleElement(title));

    // Get the body
    HtmlContainer body = page.getBody();

    // Make a table, and add it to the body (even before it's filled)
    TableElement tab = new TableElement()
                       .setCellPadding(0)
                       .setCellSpacing(0)
                       .setBorder(1)
                       .setWidth("60%");
    body.addElement(new CenteredElement(tab));

    // Create the first row
    HtmlElement conditions = new CurrentStyle("Current Conditions");
    HtmlElement image = currentImage;
    HtmlElement temp = new CurrentStyle(currentTemp + "&#176;"); // degree symbol
    tab.addElement(new TableRowElement()
                   .addElement(new TableDataElement(conditions))
                   .addElement(new TableDataElement(image))
                   .addElement(new TableDataElement(temp)
                               .setColSpan(2)));

    // Create the second row
    HtmlElement extended = new ExtendedTitleStyle("Extended Forecast");
    HtmlElement hi = new ExtendedTitleStyle("Hi");
    HtmlElement lo = new ExtendedTitleStyle("Lo");
    tab.addElement(new TableRowElement()
                   .addElement(new TableDataElement(extended)
                               .setColSpan(2))
                   .addElement(new TableDataElement(hi))
                   .addElement(new TableDataElement(lo)));

    // Create the forecast rows
    for (int i = 0; i < forecastDay.length; i++) {
      HtmlElement day = new ExtendedDayStyle("&nbsp;" + forecastDay[i]);
      HtmlElement daypic = forecastImage[i];
      HtmlElement dayhi = new ExtendedTempStyle("" + forecastHi[i]);
      HtmlElement daylo = new ExtendedTempStyle("" + forecastLo[i]);
      tab.addElement(new TableRowElement()
                   .addElement(new TableDataElement(day))
                   .addElement(new TableDataElement(daypic))
                   .addElement(new TableDataElement(dayhi))
                   .addElement(new TableDataElement(daylo)));
    }

    // Send the page to the response's output stream
    page.output(res.getOutputStream());
  }
}

This servlet uses five support classes to define custom styles for portions of the generated web page. For example, CurrentStyle defines the font and positioning for the elements that display the current conditions, while ImageStyle defines the size and positioning of the forecast icons. Each support class is a subclass of HtmlElement (though not always directly) and can thus be treated like a first-class component on the web page.

Custom styles further abstract the HTML components on the page. What was once a String surrounded by HTML tags is now a high-level page component. A servlet can fill these components with content and not worry about exactly how they will be displayed. Their display is left to the style class. Should it happen that the appearance needs to be changed, such as when you decide you want the extended forecast font to be bold, the change can be done with a single modification to the appropriate style.

Subclassing also proves useful for more mundane tasks. It can be used to define basic HTML components that, for whatever reason, are not included in the HTML generation package. For example, htmlKona has no ServletElement class to represent an embedded <SERVLET> tag. This class could be written similarly to its AppletElement class by subclassing htmlKona's ElementWithAttributes class.

Notice how this servlet has changed its representation of the sunny and rainy images. The previous servlets stored these images as String objects representing image locations. This servlet, however, creates each one as an ImageStyle object with an inherent size and width. This means they can be added directly to the page, simplifying the code in which they are used. It also shows how a servlet can reuse an HTML component.

For a better demonstration of reuse, imagine the TableElement created by this servlet being cached and resent in response to every request. This is simple to accomplish using the techniques demonstrated in Chapter 3, "The Servlet Life Cycle". The table could be on a page surrounded by rotating ad banners, but it can persist as an object between requests.

But what happens when the current temperature changes? Does the table have to be entirely regenerated? Not at all. Remember, the table is an object filled with other objects. All we need to do is replace the object that represents the current temperature. For our example this can be done with one line of code (note "&#176;" is the HTML representation of the degree symbol):

tab.setCellAt(0, 2, new CurrentStyle(newTemp + "&#176;"));

The possible creative uses for object-oriented HTML generation go far beyond the techniques shown in this example. One could imagine a custom-created BannerElement displayed at the top of all the servlets on a site. It could be just a predefined ImageElement or a conglomeration of elements. Let your imagination run wild!

5.4.2.4. HTML generation and databases

Before we conclude our discussion of HTML generation, there is one more feature to discuss: its potential close integration with a database. It's not by coincidence that WebLogic packages htmlKona with its database-centric dbKona and jdbcKona--the packages work well together. We'll leave the details to WebLogic's web site, but the general idea is that when you execute a query against a database, the returned result set can be thought of as a formatted table without a graphical representation. This result set table can be passed to the TableElement constructor to automatically display the query results in an HTML table on a web page.

The TableElement constructor also accepts java.util.Dictionary objects (the superclass of java.util.Hashtable and java.util.Properties). By sub-classing TableElement, it is possible to have it accept even more types, thus making it easy to create tables from all different kinds of data. A subclass can also give special treatment to certain types of data, perhaps converting them into hyperlinks to other queries.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.