In most of the preceding chapters, we have presented a general view of SVG and have tried to be application-agnostic. The techniques you've seen can be applied to diagrams destined for print, for conversion of legacy data to a more transportable format, and, of course, for web graphics. In this chapter, we will consider the problem of accessing data in XML format, transforming it to SVG, and then sending it to a client on the Web.

Contents

Serving Web Files -- The Task at Hand

We will present a list of airports on a web page, and let the users select one whose weather report they wish to see. The request will be sent to a Java servlet, which will retrieve the information in Weather Observation Markup Format (OMF) and send back a web page containing a graphic presentation of the data. Though SVG is the most compact representation — and this is a book about SVG, after all — some users may not have an SVG plugin for their browsers. Thus, we will offer them a choice of receiving the graphic in SVG or in one of two rasterized formats: PNG (Portable Network Graphics) or JPG. We offer JPG for users with older browsers that don't support PNG. The web page is shown in Figure 13-1.

This example uses a servlet without explaining the servlet mechanism in any great detail. You can find that information in Java Servlet Programming by Jason Hunter and William Crawford, published by O'Reilly & Associates.

Figure 13-1. Screenshot of web page

The resulting graphic will be a modified form of the graphic presented in Chapter 12, in Section 12.3, showing the city name, time, temperature, wind speed and direction, and visibility. The most important modification is that the graphic will show the local time, rather than Greenwich Mean Time, which is the format for the original data. Figure 13-2 shows one such result.

Figure 13-2. Screenshot of resulting graphic

Partitioning the Task

Our task can be broken down into three major subtasks: creating the web page, writing the servlet itself, and constructing the XSLT file that the servlet will use.

The Request Web Page

Example 13-1 is a listing of the HTML for the web page. The value of each select menu option consists of the four-letter station ID, city name, and time zone, separated by vertical bars. The time zone is chosen from the list provided by Java's java.util.TimeZone.getAvailableIDs() function. The action attribute points to the servlet running on localhost; you will change this depending on the server, server software, and servlet container you're using.

The Weather Servlet

Let us now turn our attention to the Java servlet, Weather, that creates the web page on which the image will appear. This servlet merely creates HTML; it doesn't create the image itself. Instead, it will create an <embed> tag (or <img> tag) whose src attribute will be a reference to the Transform servlet that does the actual transformation. There are two reasons to do this: First, most applications create a web page with an image rather than returning an image alone. Second, returning only an image causes caching problems with some browsers; returning a web page doesn't. The source code for the servlet is listed in Example 13-2.

[3] The servlet sets the content type for a web page (text/html) and emits an inordinate amount of information to make sure the page doesn't get cached.

[4] Retrieve the weather station call letters (call_id) and the URL of the web page from which we were called (the referer). The referrer, whose keyword was misspelled when the API was created, will be null if the user typed the URL in the browser's location bar rather than calling the servlet from an actual web page.

[5] Split out the city name from the call_id parameter for use in the <title> element.

[6] If there was a referring page, create a link back to that page.

[7] Create the appropriate <object> or <img> tag. Note that the src attribute is a URL that will call the Transform servlet, passing on to it all the parameters that we received in this servlet.

[8] If this servlet is called from a GET request, handle it exactly the same as a POST request.

The Transform Servlet

When the Weather servlet is invoked, it will create a web page, one of whose tags will in turn invoke the Transform servlet. This servlet has to retrieve the XML, transform it to SVG, possibly convert it to JPG or PNG format, and send it to the client, as shown in Figure 13-3. We want a cross-platform, open source solution, so we will use Apache's Xalan processor for XSLT, and the Apache Batik project's transcoder.

The init function will read the XSL transformation file, compile it, and store the result in the class variable xslTemplate. This function is called only once, and the variable will persist throughout the servlet's lifetime. This means we don't have to re-parse the XSL file every time the servlet is called.

Here's the routine that accesses the OMF source. The particular source we're using will only accept POST requests; this code shows you how to do them. The OMF source we used while developing this chapter returns an XML file starting with a <!DOCTYPE ... > if the input data is valid; otherwise it returns an HTML page with an error message.

Sending back the JPG and PNG requires one extra step: invoking the appropriate Batik transcoder to convert the SVG into an array of bytes, which we send directly to the response's output stream. response.getWriter() is used for text; response.getOutputStream() is used for binary data.

We are now left with one interesting problem — handling errors. In the Weather servlet, we were creating an HTML page, so the error trapping simply generated a different HTML page with the error message on it. In this case, though, the servlet is expecting image data, and sending back HTML text won't do. The client wants image data, so that's what we'll give it. If the user requested an SVG graphic, we'll send back the following "error image" from a text file:

If the user requested a JPG or PNG image, we'll send back a JPG version of the error image, shown at half size in Figure 13-4. We send back a JPG image because that format is supported by even the oldest browsers.

Figure 13-4. Screenshot of error image

Here's the code. Again, the SVG, being text, is sent to response.getWriter(), and the JPG, being binary, is sent to response.getOutputStream(). If there's any error during this process, we log the error and let the bits fall where they may.

The XSLT File

The XSLT file used to transform the OMF records to SVG has one major enhancement; it must receive the parameter that was passed to it in the doPost method. Note that we give a default value of UTC so that the result will show up in Greenwich Mean Time if no parameter is passed to the XSLT file.

<xsl:param name="timeZone" select="UTC"/>

The XSLT extensions we wrote in Chapter 12 in Section 12.3.4 must also be modified to use the time zone parameter when returning the date and time. These extensions are in the XSLTUtils.java file, which will be compiled and stored in a file named XSLTUtils.jar (rather than TimeUtils.class as in the original example).

Setting up the Server

We chose the Tomcat servlet container implementation from the Apache Software Foundation, found at http://jakarta.apache.org/index.html, to run these servlets. As with all the other software mentioned in this appendix, it is cross-platform and open source. It can run as a standalone server, serving web pages, servlets, and JavaServer Pages. It can also be used in conjunction with the Apache web server, Microsoft's Internet Information Server (IIS), Microsoft's Personal Web Server, or Netscape's Netscape Enterprise Server.[1]

We did our testing with Tomcat running in standalone mode on a Linux system. We changed the tomcat.sh shell script as follows:

We explicitly set the JAVA_HOME and TOMCAT_HOME variables:

JAVA_HOME=/usr/local/j2sdk1.3.0
TOMCAT_HOME=/usr/local/jakarta-tomcat

To run our servlets, Tomcat's classpath needs access to Xalan and Xerces for the XSLT transformations, and it also needs access to the Batik .jar files. The normal Tomcat classpath setup is also in conf/tomcat.sh. It saves your current CLASSPATH, creates a new one with the paths that it wants, and then re-appends the classpath that you had specified. We modified that code to create a new classpath and entirely ignore the old one, which had some duplications and many unnecessary paths. Our changes are shown in boldface.

oldCP=$CLASSPATH
unset CLASSPATH
## The latest versions of Xalan and Xerces which we# want to use must come first!#CLASSPATH=/usr/local/xmljar/xalan.jar:/usr/local/xmljar/xerces.jarCLASSPATH=${CLASSPATH}:/usr/local/xmljar/bsf.jarCLASSPATH=${CLASSPATH}:/usr/local/xmljar/XSLTUtils.jar## Add all the .jar files in the Batik library# to the classpath#for i in /usr/local/batik/lib/* ; do CLASSPATH=${CLASSPATH}:$idone## Add all the .jar files in Tomcat's library directory# to the classpath. The "else" branch will never be# taken, but it's needed in Tomcat's code, so we decided# to leave it intact here#
for i in ${TOMCAT_HOME}/lib/* ; do
if [ "$CLASSPATH" != "" ]; then
CLASSPATH=${CLASSPATH}:$i
else
CLASSPATH=$i
fi
done
if [ -f ${JAVA_HOME}/lib/tools.jar ] ; then
# We are probably in a JDK1.2 environment
CLASSPATH=${CLASSPATH}:${JAVA_HOME}/lib/tools.jar
fi
# Backdoor classpath setting for development purposes when all classes
# are compiled into a /classes dir and are not yet jarred.
if [ -d ${TOMCAT_HOME}/classes ]; then
CLASSPATH=${TOMCAT_HOME}/classes:${CLASSPATH}
fi
# Ignore old classpath altogether##if [ "$oldCP" != "" ]; then# CLASSPATH=${CLASSPATH}:${oldCP}#fi

We added a new context to the conf/server.xml so we could put our files in a directory other than the webapps/examples directory. We set reloadable to true because we were doing a lot of recompiling and testing. In a production environment you would probably want to set this to false to avoid the overhead of checking for updates every time a request comes to the server.