Search the Catalog
Java and XML

Java and XML

By Brett McLaughlin
1st Edition June 2000
0-596-00016-2, Order Number: 0162
498 pages, $39.95

Chapter 9
Web Publishing Frameworks

In this chapter:
Selecting a Framework
Installation
Using a Publishing Framework
XSP
Cocoon 2.0 and Beyond
What's Next?

This chapter begins our look at specific Java and XML topics. So far, we have covered the basics of using XML from Java, looking at the SAX and DOM APIs to manipulate XML and the fundamentals of using and creating XML itself. We've also looked at how JDOM can provide a more Java-centric means of using our XML data and documents within Java programs. Now that you have a grasp on using XML from your code, we will spend time on specific applications. The next six chapters represent the most significant applications of XML, and, in particular, how those applications are implemented in the Java space. While there are literally hundreds and soon to be thousands of important applications of XML, the topics in these chapters are those that continually seem to be in the spotlight, and that have a significant potential to change the way traditional development processes occur.

We begin our look at these hot topics with the one XML application that seems to have generated the largest amount of excitement in the XML and Java communities: the web publishing framework. Although we have continually emphasized that generating presentation from content is perhaps over-hyped when compared to the value of the portable data that XML provides, using XML for presentation styling is still very important. This importance increases when looking at web-based applications.

Over the next five years, virtually every major application will either be completely web-based, or at a minimum have a web frontend. At the same time, users are demanding more functionality, and marketing departments are demanding more flexibility in look and feel. The result has been the rise of the web artist; this new role is different from the webmaster in that little to no Perl, ASP, JavaScript, or other scripting language coding is part of the job description. The web artist's entire day is comprised of HTML creation, modification, and development. The rapid changes in business and market strategy can require a complete application or site overhaul as often as once a week, often forcing the web artist to spend days changing hundreds of HTML pages. While Cascading Style Sheets (CSS) have helped, the difficulty of maintaining consistency across these pages has required a huge amount of time. Even if this less than ideal situation were acceptable, no computer developer wants to spend his or her life making HTML changes to web pages.

With the advent of server-side Java, this problem has only grown. Servlet developers find themselves spending long hours modifying their out.println( ) statements to output HTML, and often glance hatefully at the marketing department when changes to a site's look require modifications to their code. The entire Java Server Pages ( JSP) specification arguably stemmed from this situation; however, JSP is not a solution, as it only shifts the frustration to the HTML developer, who constantly has to avoid making incidental changes to embedded Java code. In addition, JSP does not provide the clean separation between content and presentation it promises. What was called for was a means to generate pure data content, and have that content uniformly styled either at predetermined times (static content generation), or dynamically at runtime (dynamic content generation).

Of course, you should be nodding your head at this familiar problem if you have ever done any web development, and hopefully your mind is wandering into the XSL and XSLT technology space. The problem is that an engine must exist to handle content generation, particularly in the dynamic sense. Having hundreds of XML documents on a site does no good if there is no mechanism to apply transformations on them when requested. Add to this the need for servlets and other server-side components to output XML that should be consistently styled, and you have defined a small set of requirements for the web publishing framework. In this chapter, we take a look at this framework, how it can allow you to toss out those long hours of HTML coding, and how it can help you convert all of those "web artists" into XML and XSL gurus, making you happy, them happy, and allowing your applications to change look and feel as often as you want.

A web publishing framework attempts to address these complicated issues. Just as a web server is responsible for responding to a URL request for a file, a web publishing framework is responsible for responding to a similar request; however, instead of responding with a file, it often will respond with a published version of a file. In this case, a published file refers to a file that may have been transformed with XSLT, or massaged at an application level, or converted into another format such as a PDF. The requestor does not see the raw data that may underlie the published result, but also does not have to explicitly request that publication occur. Often, a URI base (such as http://yourHost.com/publish) signifies that a publishing engine that sits on top of the web server should handle requests. As you may suspect, the concept is much simpler than the actual implementation of a framework like this, and finding the correct framework for your needs is not a trivial task.

Selecting a Framework

If you're getting an idea of the importance of the web publishing framework, you might expect to find a list of hundreds of possible solutions. This is because the Java language offers an easy interface into the various XML tools used by web publishing frameworks. Additionally, Java servlets offer a simple means of handling web requests and responses. However, the list of frameworks is small, and the list of good and stable ones is even smaller. One of the best resources for seeing what products are currently available is XML Software's list at http://xmlsoftware.com/publishing/. This list changes frequently enough that it is not worth repeating here. Still, some important criteria for determining what framework is right for you are worth mentioning.

Stability

Don't be surprised if you have a hard time finding a product whose version tag is greater than 2.x. In fact, you may have to search diligently to even find a second-generation framework. While a higher version number is not a guarantee of stability, it often reflects the amount of time, effort, and review that a framework has undergone. The XML publishing system is such a new beast that the market is being flooded with 1.0 and 1.1 products that simply are not stable enough for practical use.

You can also often ascertain stability of a product by the stability of other products from the same vendor. Often, an entire suite of tools is released by a vendor; if their other tools do not offer SAX 2.0 and DOM Level 2 support, or are all also 1.0 and 1.1 products, you might be wise to pass on the framework until it has matured a little more, and has conformed to newer XML standards. You should also try to steer away from platform-specific technologies -- if the framework is tied to a platform (such as Windows), you aren't dealing with a pure Java solution. Remember that a publishing framework must serve clients on any platform; why be happy with a product that can't also run on any platform?

Integration with Other XML Tools and APIs

Once you have ensured that your framework is stable enough for your needs, you should make sure that it has support for a variety of XML parsers and processors. If a framework is tied to a specific parser or processor, you are really just buying an XML version of Microsoft -- you have tied yourself to one specific implementation of a technology. Although frameworks often integrate well with a particular parser vendor, determine if parsers can be interchanged. If you have a favorite processor (or one left to you from previous projects), make sure that processor can still be used.

Support for SAX and DOM is a must. Also, try to find a framework whose developers are monitoring the specifications of XML Schema, XLink, XPointer, and other emerging XML technologies. This will indicate if you can expect to see revisions of the framework add support for these XML specifications, an important indication of the framework's longevity. Don't be afraid to ask questions about how quickly new specifications can be expected to be integrated into the product, and insist on a firm answer.

Production Presence

The last, and perhaps most important, question to answer when looking for a web publishing framework is determining if it is used in production applications. If you cannot be supplied with at least a few reference applications or sites that are using the framework, don't be surprised if there aren't any. Vendors (and developers, in the open source realm) should be happy and proud to let you know where you can check out their frameworks in action. Hesitance in this area is a sign that you may be more of a pioneer with a product than you wish to be.

Making the Decision

Once you have evaluated these criteria, you will probably have a clear answer. Very few frameworks can positively answer all the questions raised here, not to mention your application specific concerns. In fact, at the time of this writing, less than five publishing frameworks exist that support the latest versions of SAX, DOM, and JAXP, are in production at even one application site, and have at least three significant revisions of code under their belt. These are not listed here because, honestly, in six months they may not exist, or may be radically changed. The world of web publishing frameworks is in such flux that trying to recommend you to four or five options and be assured they will be in existence months from now has a greater chance of misleading you than helping you.

However, one publishing framework has consistently succeeded and received notice within the Java and XML community; when considering the open source community in particular, this framework is often the choice of Java developers. The Apache Cocoon project, founded by Stefano Mazzocchi, has been a solid framework since its inception. Developed while most of us were still trying to figure out what XML was, Cocoon is now entering its second generation as an XML publishing framework based completely in Java. It also is part of the Apache XML project, and has default support for Apache Xerces and Apache Xalan. It allows any conformant XML parser to be used, and is based on the immensely popular Java servlet architecture. In addition, there are several production sites using Apache Cocoon (in its 1.x form) that push the boundaries of traditional web application development yet still perform extremely well. For this reason, and again in keeping with the spirit of open source software, we use Apache Cocoon as the framework of choice in this chapter.

In previous chapters, our choice of XML parser and processor was fairly open; in other words, examples would work with only small modifications to code when using different vendor implementations. However, the web publishing framework is not standardized, and each framework implements wildly different features and conventions. For this reason, the examples in this chapter using Apache Cocoon are not portable; however, the popularity of the concepts and design patterns used within Cocoon merit an entire chapter on using the framework. If you do not choose Cocoon, you should at least look over the examples, as the concepts in web publishing are usable across any vendor implementation, even though the specifics of the code are not.

Installation

In other chapters, installation instructions generally involved pointing you at a web site where you could obtain a distribution of the software and letting you add the included jar file to your class path. Installing a framework such as Cocoon is not such a trivial task, and we document the procedures to make this happen here. If you want the very latest versions of the framework, you should download a copy of CVS, the Concurrent Versioning System. This allows you to obtain the code from the actual source code repository rather than the less frequent code releases, which usually occur at version releases. You can get CVS from http://www.cyclic.com/cyclic-pages/howget.html.

With Cocoon, we look at the 1.x version of the framework. Although by the time you are reading this, the public 2.0 release will probably be available in a beta form, at this time, Cocoon 2.0 is in an alpha state, and is only available through CVS access. Because of the high amount of change still anticipated in the Cocoon 2.0 framework, we will focus on the 1.x version tree, as that is being used most often today. At the end of this chapter, we do take a brief look at the upcoming features of Cocoon 2, which is scheduled for a full release late in 2000. If you are looking at using Cocoon in a production environment today, you will definitely want to stay with the 1.x branch until Cocoon 2 has been released and stabilized.

If going through the procedures you find yourself encountering problems, you should take advantage of the online resources available for Cocoon. The Apache XML project, located at http://xml.apache.org, hosts the Cocoon project. There are mailing lists available at http://xml.apache.org/mail.html, as well as a very informative FAQ at http://xml.apache.org/cocoon/faqs.html. Don't be afraid to ask questions and get involved; installation of complex application frameworks is rarely simple, and chances are that others may share your problems and frustrations. With that in mind, let's get down to business.

Getting Ant

Unix and Linux veterans are probably already gearing up to type those familiar commands when compiling source code:

/home/bmclaugh (mejis)> ./configure
/home/bmclaugh (mejis)> make
/home/bmclaugh (mejis)> make install

The association between source code, make, and autoconf is old and long-standing. However, make does not translate well with Java code -- Windows users have to have additional tools for compiling on a Windows platform, lengthy configuration has to be performed to allow Javadoc and other extra commands to be run, RMI compiles (rmic) are complex, and the list goes on. The solution designed to work so well with Perl, shell scripts, and C code is not robust enough for the Java paradigm.

Luckily, James Duncan Davidson (of Jakarta, JAXP, and the servlet specification fame) spent some long nights doing more than just complaining. He began what is now called Ant, which is part of the Apache Jakarta project. Ant is a Java-based build tool; its configuration is XML based, it is cross-platform, and can handle any task needed. RMI compiles, Javadoc, external commands, and more can all be run within this environment. It is Ant that is used for building the Cocoon sources.

The current version of Ant is included when you obtain Cocoon, and is located in the Cocoon lib/ directory. You can also get the latest version of Ant from the Jakarta web site, located at http://jakarta.apache.org. Instructions for using Ant with Cocoon are included with the Cocoon distribution, while more general documentation on Ant is available at the Jakarta web site.

Getting Cocoon

With Ant in place, you are ready to obtain the source for Cocoon 1.x. In addition to being able to download Cocoon from the Apache XML project (http://xml.apache.org), the latest version with new features is available via CVS. If you are just starting out with Cocoon, you may want to download a packaged distribution; however, by this time you should be pretty comfortable with Java and XML code. You may want to obtain the very newest version of Cocoon, the 1.x.dev version, from the Apache XML CVS repository. You can get the code from CVS with:

cvs -d :pserver:anoncvs@xml.apache.org:/home/cvspublic login
Password: ******* (Password is 'anoncvs')
 
cvs -d :pserver:anoncvs@xml.apache.org:/home/cvspublic checkout xml-cocoon
...

You will then get the Cocoon source distribution in the xml-cocoon directory. This contains the build file used by Ant, all the required libraries to build Cocoon, and the source itself. Change into the created directory and you are ready to build Cocoon.

Building Cocoon

At this point, you need to be sure you are back into the main directory of the Cocoon project. To perform the build, enter the following command on Windows systems:

D:\dev\xml-cocoon> build.bat

A shell script is provided for use on Unix and Linux systems:

$ sh build.sh

The lib/ subdirectory contains all the libraries needed for building Cocoon. These supplied build scripts will add each jar file in this directory to your class path, which includes the latest versions of Apache Xerces, Apache Xalan, and other dependencies that work with Cocoon 1.x. Even if you already have some of these libraries (such as Xerces or Xalan), it is recommended that you use the supplied libraries (which the scripts take care of ), as they are certified to work with the version of Cocoon retrieved from CVS. When finished, your class path will include the following libraries:

The build script then tells Ant to use the build.xml in the current directory to build the project. Once executed, your output should look like this:

Cocoon Build System
-------------------
Building with classpath /usr/java/lib/tools.jar;./lib/xerces_1_0_3.jar;
./lib/xalan_1_0_0.jar;./lib/fop_0_12_1.jar;./lib/servlet_2_2.jar;
./lib/ant.jar
Starting Ant...
Buildfile: build.xml
Project base dir set to: /home/bmclaugh/projects/cocoon
Executing Target: init
------------------- Cocoon 1.7.3-dev [1999-2000] ----------------
Executing Target: prepare
Created dir: /home/bmclaugh/projects/cocoon/build
Executing Target: prepare-projectx
Copying 1 files to /home/bmclaugh/projects/cocoon/src
Executing Target: prepare-xt
Executing Target: prepare-ecma
Executing Target: prepare-ldap
Copying 5 files to /home/bmclaugh/projects/cocoon/build/src
Executing Target: prepare-src
Created dir: /home/bmclaugh/projects/cocoon/build/classes
Copying 109 files to D:\dev\xml-cocoon\build\src
Executing Target: compile
Compiling 98 source files to /home/bmclaugh/projects/cocoon/build/classes
Copying 12 support files to /home/bmclaugh/projects/cocoon /build/classes
Executing Target: package
Building jar: /home/bmclaugh/projects/cocoon/build/cocoon.jar
Completed in 24 seconds

You may see slight differences in the version or exact number of files, but no errors should occur; if they do, make the corrections to your class path indicated by the build program and re-run the build command. At the end of this process, you should have the complete Cocoon distribution in a single jar file, cocoon.jar, located in the build/ subdirectory. You should also verify that you have the sample properties file that Cocoon comes with, cocoon.properties, in the bin/ directory of the project. If you cannot locate this file, it is also located in the build/classes/org/apache/cocoon/ subdirectory.

You can also use Ant to generate documentation, Javadoc, and perform other tasks related to the project. These tasks are accomplished by specifying targets to the build command. Targets are keywords supplied as arguments to Ant; the complete list of supported targets for Cocoon is listed in the build file, build.xml. The target for documentation is docs, and for Javadoc it is javadocs. For example, to generate the Cocoon project documentation, perform:

$ sh build.sh docs
Building with classpath /usr/java/lib/tools.jar;./lib/xerces_1_0_3.jar;
./lib/xalan_1_0_0.jar;./lib/fop_0_12_1.jar;./lib/servlet_2_2.jar;
./lib/ant.jar
Starting Ant...
Buildfile: build.xml
Project base dir set to: /home/bmclaugh/projects/cocoon
Executing Target: init
------------------- Cocoon 1.7.3-dev [1999-2000] ----------------
Executing Target: prepare-docs
Replacing ./docs/dtd/ --> dtd/
Replacing ./docs/dtd/ --> dtd/
Executing Target: docs
...

This generates complete project documentation in the build/docs/ subdirectory. Once you have built Cocoon and any desired optional targets, you should be ready to set up your servlet engine to use Cocoon.

Configuring the Servlet Engine

Once you have built Cocoon, you need to configure your servlet engine to use Cocoon and tell it which requests Cocoon should handle. We look at setting up Cocoon to work with the Jakarta Tomcat servlet engine here;[3] as this is the reference implementation for the Java Servlet API 2.2, you should be able to mimic these steps for your own servlet engine if you are not using the Tomcat implementation.

The Cocoon framework is built to operate at an engine level rather than as another servlet in your engine. Therefore, we need to add Cocoon and its dependencies to the core servlet engine class path rather than in a particular servlet zone or context. Copy the Cocoon jar file and the Xerces, Xalan, and FOP jar files into the Tomcat lib/ subdirectory, off of the main Tomcat installation directory. You then need to add these libraries to the engine class path; in Tomcat, this is accomplished through editing the Tomcat initialization script. For Windows platforms, this is <TOMCAT_HOME>/bin/tomcat.bat ; for Unix platforms, <TOMCAT_HOME>/bin/tomcat.sh. In these files, you will see the lines that set the class path used by Tomcat when starting up. You should add the Cocoon distribution and its dependencies before these other entries in the configuration file. On Windows, this will look like:

set CLASSPATH=.
set CLASSPATH=%TOMCAT_HOME%\classes
 
rem Cocoon classes and libraries
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xerces_1_0_3.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xalan_1_0_0.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\fop_0_12_1.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\cocoon.jar
 
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\webserver.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\jasper.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xml.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\servlet.jar
set CLASSPATH=%CLASSPATH%;%JAVA_HOME%\lib\tools.jar

On Unix platforms, the modified file should look like this:

CLASSPATH=.
 
# Cocoon classes and libraries
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xerces_1_0_3.jar
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xalan_0_20_0.jar
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/fop_0_12_1.jar
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/cocoon.jar
 
for i in ${TOMCAT_HOME}/lib/* ; do
  CLASSPATH=${CLASSPATH}:$i
done
 
CLASSPATH=${CLASSPATH}:${JAVA_HOME}/lib/tools.jar

It is important to ensure that the Cocoon classes precede the rest of the Tomcat classes, particularly xml.jar. While Xerces, Xalan, and Cocoon all use SAX 2 and DOM Level 2 classes and interfaces, the Sun Project X parser contained in xml.jar does not yet have SAX 2 and DOM Level 2 support; if this class and its SAX and DOM versions are found first, the Cocoon framework will error out.

With these libraries added, all that is left is to specify to Cocoon the location of its properties file (we will look at what this file does a little later). Copy the cocoon.properties file from the Cocoon root directory into <TOMCAT_HOME>/conf/. In this same directory, you will see web.xml, which configures the properties for engine-wide servlets. In this file are properties and mappings for various engine-level servlets; we need to add configuration for Cocoon here. Insert the following entries into the engine configuration file:

<servlet>
    <servlet-name>
        org.apache.cocoon.Cocoon
    </servlet-name>
    <servlet-class>
        org.apache.cocoon.Cocoon
    </servlet-class>
    <init-param>
        <param-name>
            properties
        </param-name>
        <param-value>
            /usr/lib/jakarta-tomcat/conf/cocoon.properties
        </param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>
        org.apache.cocoon.Cocoon
    </servlet-name>
    <url-pattern>
        *.xml
    </url-pattern>
</servlet-mapping>

The location within the file does not matter, as long as you ensure that element nestings are not disrupted; in other words, the resulting file must remain well-formed XML. You will also need to insert the correct path to the cocoon.properties file for the value of the properties parameter. This tells the engine to pass this parameter to the main Cocoon servlet, enabling it to configure itself and the rest of the Cocoon framework. The servlet-mapping then instructs the engine to direct all URI requests that end in .xml to the Cocoon framework. With these changes made, you can start (or restart) Tomcat; ensure that no errors occur and the Cocoon install can be tested.

If everything has been configured correctly, you should be able to access http://<hostname>:<port>/Cocoon.xml in your web browser. If no errors occur, the HTML output should look like that shown in Figure 9-1.

Figure 9-1. The output of the Cocoon configuration URI when properly configured

 

If there are errors, you should see a stack trace indicating what problems were encountered. These typically relate to classes not being in the engine class path that Cocoon needs, the properties file not being specified in an initial argument to the servlet, or the file specified being unreadable by the servlet. Correct any errors that result, and restart the engine. Once you receive the output above, you are ready to see Cocoon in action and configure Cocoon to handle a variety of requests.

Using a Publishing Framework

Using a good publishing framework like Cocoon doesn't require any special instruction; it is not a complex application that users must learn to adapt to. In fact, all the uses of Cocoon are based on simple URLs entered into a standard web browser. Generating dynamic HTML from XML, viewing XML transformed into PDF files, and even seeing VRML applications generated from XML is simply a matter of typing the URL to the desired XML file into your browser and watching Cocoon and the power of XML take action.

Viewing XML Converted to HTML

Now that our framework is in place and correctly handling requests that end in .xml, we can begin to see it publish our XML files. Cocoon comes with several sample XML files and associated XSL stylesheets in the project's samples/ subdirectory. However, we have our own XML and XSL from earlier chapters, so let's transform the partial XML table of contents for our book with the XSL stylesheet we built in Chapter 6, Transforming XML. The XML file should be named contents.xml (and is also available from the book's web site). Locate where you saved this file, and copy it into the servlet engine's document root. On a default installation of Tomcat, this is under <TOMCAT_ROOT>/webapps/ROOT/. The document refers to the stylesheet XSL/JavaXML.html.xsl. Create the XSL directory in your web document root, and copy the stylesheet we built in Chapter 6 into that directory. You should make sure that the DTD referred to in the XML document is commented out (remember, validation should rarely occur in production); also convert the OReillyCopyright entity reference to HTML as discussed in Chapter 6. Although validation and external entity references are supported by Cocoon, it is easier to view our XML without worrying about those details for now.

Once you have the XML document and its stylesheet in place, you should be able to access it with the URL http://<hostname>:<port>/contents.xml in your web browser. If you made all the modifications discussed in Chapter 6, the transformed XML should look like Figure 9-2.

Figure 9-2. Transformed XML from Chapter 6

 

This should have seemed almost trivial to you; once Cocoon is set up and configured, serving up dynamic content is a piece of cake! The mapping from XML extensions to Cocoon should work across your entire servlet engine.

Viewing PDFs from XML

So far, we have talked almost exclusively about converting XML documents to HTML; when not looking at this, we have assumed our data was being used in an application-to-application manner. The format was entirely arbitrary, as both the sending and receiving applications parsed the XML using the specified DTD or schema. However, a publishing framework offers many more possibilities. Not only are a variety of markup languages supported as final document formats, but in addition, Java provides libraries for converting XML to some non-markup-based formats. The most popular and stable library in this category is the Apache XML group's Formatting Objects Processor, FOP, which we discussed briefly in Chapter 6. This gives Cocoon or any other publishing framework the ability to turn XML documents into Portable Document Format (PDF) documents, which are generally viewed with Adobe Acrobat (http://www.adobe.com).

The importance of being able to convert a document from XML into a PDF cannot be overstated; particularly for document-driven web sites, such as print media or publishing companies, this could revolutionize web delivery of data. Consider the following XML document, an XML-formatted excerpt from Chapter 1, Introduction, shown in Example 9-1.

Example 9-1: XML Version of Chapter 1

<?xml version="1.0"?>
 
<?cocoon-process type="xslt"?>
<?xml-stylesheet href="XSL/JavaXML.fo.xsl" type="text/xsl"?>
 
<book>
 <cover>
  <title>Java and XML</title>
   <author>Brett McLaughlin</author>
 </cover>
 
 <contents>
  <chapter id="chapterOne">
   <title>Chapter 1: Introduction</title>
      
   <paragraph>XML.  These three letters have brought shivers to 
   almost every developer in the world today at some point in the
   last two years.  While those shivers were often fear at another
   acronym to memorize, excitement at the promise of a new technology,
   or annoyance at another source of confusion for today's 
   developer, they were shivers all the same.  Surprisingly, almost every
   type of response was well merited with regard to XML.  It is another 
   acronym to memorize, and in fact brings with it a dizzying array of 
   companions: XSL, XSLT, PI, DTD, XHTML, and more.  It also brings with 
   it a huge promise -- what Java did for portability of code, XML claims 
   to do for portability of data.  Sun has even been touting the 
   rather ambitious slogan "Java + XML = Portable Code + Portable 
   Data" in recent months.  And yes, XML does bring with it a 
   significant amount of confusion.  We will seek to unravel and 
   demystify XML, without being so abstract and general as to be 
   useless, and without diving in so deeply that this becomes just 
   another droll specification to wade through.  This 
   is a book for you, the Java developer, who wants to understand the 
   hype and use the tools that XML brings to the table.</paragraph>
 
   <paragraph>Today's web application now faces a wealth of problems
   that were not even considered ten years ago.  Systems that are 
   distributed across thousands of miles must perform quickly and 
   flawlessly.  Data from heterogeneous systems, databases, directory 
   services, and applications must be transferred without a single 
   decimal place being lost.  Applications must be able to communicate 
   not only with other business components, but other business systems 
   altogether, often across companies as well as technologies.  Clients 
   are no longer limited to thick clients, but can be web browsers that 
   support HTML, mobile phones that support Wireless Application 
   Protocol (WAP), or handheld organizers with entirely different markup 
   languages altogether. Data, and the transformation of that data, has 
   become the crucial centerpiece of every application being developed 
   today.</paragraph>
  </chapter>
 
 </contents>
</book>

We have already seen how XSL stylesheets allow us to transform this document into HTML. But converting an entire chapter of a book into HTML could result in a gigantic HTML document, and certainly an unreadable format; potential readers wanting online delivery of a book generally would prefer a PDF document. On the other hand, generating PDF statically from the chapter means changes to the chapter must be matched with subsequent PDF file generation. Keeping a single XML document format means the chapter can be easily updated (with any XML editor), formatted into SGML for printing hard copy, transferred to other companies and applications, and included in other books or compendiums. Now add to this robust set of features the ability for web users to type in a URL and access the book in PDF format, and you have a complete publishing system.

Although we don't have the time to cover formatting objects and the FOP for Java libraries in detail, you can review the entire formatting objects definition within the XSL specification at the W3C at http://www.w3.org/TR/xsl/. Example 9-2 is an XSL stylesheet that uses formatting objects to specify a transformation from XML to a PDF document, appropriate for our XML version of Chapter 1.

Example 9-2: XSL Stylesheet to Transform Example 9-1 into a PDF Document

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fo="http://www.w3.org/1999/XSL/Format">
 
  <xsl:template match="book">
    <xsl:processing-instruction name="cocoon-format">
      type="text/xslfo"
    </xsl:processing-instruction>
    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
      <fo:layout-master-set>
      <fo:simple-page-master
        page-master-name="right"
        margin-top="75pt"
        margin-bottom="25pt"
        margin-left="100pt"
        margin-right="50pt">
        <fo:region-body margin-bottom="50pt"/>
        <fo:region-after extent="25pt"/>
      </fo:simple-page-master>
      <fo:simple-page-master
        page-master-name="left"
        margin-top="75pt"
        margin-bottom="25pt"
        margin-left="50pt"
        margin-right="100pt">
        <fo:region-body margin-bottom="50pt"/>
        <fo:region-after extent="25pt"/>
      </fo:simple-page-master>
      </fo:layout-master-set>
 
      <fo:page-sequence>
 
        <fo:sequence-specification>
          <fo:sequence-specifier-alternating
            page-master-first="right"
            page-master-odd="right"
            page-master-even="left"/>
        </fo:sequence-specification>
 
        <fo:static-content flow-name="xsl-after">
          <fo:block text-align-last="centered" font-size="10pt">
            <fo:page-number/>
          </fo:block>
        </fo:static-content>
 
        <fo:flow>
          <xsl:apply-templates/>
        </fo:flow>
      </fo:page-sequence>
 
    </fo:root>
  </xsl:template>
 
  <xsl:template match="cover/title">
    <fo:block font-size="36pt" text-align-last="centered" 
              space-before.optimum="24pt">
      <xsl:apply-templates/>
    </fo:block>
  </xsl:template>
 
  <xsl:template match="author">
    <fo:block font-size="24pt" text-align-last="centered" 
              space-before.optimum="24pt">
      <xsl:apply-templates/>
    </fo:block>
  </xsl:template>
 
  <xsl:template match="chapter">
    <xsl:apply-templates/>
  </xsl:template>
 
  <xsl:template match="chapter/title">
    <fo:block font-size="24pt" text-align-last="centered" 
              space-before.optimum="24pt">
      <xsl:apply-templates/>
    </fo:block>
  </xsl:template>
 
  <xsl:template match="paragraph">
    <fo:block font-size="12pt" space-before.optimum="12pt" 
              text-align="justified">
      <xsl:apply-templates/>
    </fo:block>
  </xsl:template>
</xsl:stylesheet>

If you create both of these files, saving the chapter as chapterOne.xml, and the XSL stylesheet as JavaXML.fo.xsl within a subdirectory called XSL/, you can see the result of the transformation in a web browser. Make sure you have the Adobe Acrobat Reader and plug-in for your web browser, and then access the XML document just created. Figure 9-3 shows the results.

Figure 9-3. PDF document from Example 9-1 and Example 9-2.

 

Browser-Dependent Styling

In addition to specifically requesting certain types of transformations, such as a conversion to a PDF, Cocoon allows for dynamic processing to occur based on the request. A common example of this is applying different formatting based on the media of the client. In a traditional web environment, this would allow an XML document to be transformed differently based on the browser being used. A client using Internet Explorer could be served a different presentation than a client using Netscape; with the recent wars between versions of HTML, DHTML, and JavaScript brewing between Netscape and Microsoft, this is a powerful feature to have available. Cocoon provides built-in support for many common browser types. Locate the cocoon.properties file you referenced earlier, open it, and scroll to the bottom of the file. You will see the following section (this may be slightly different for newer versions):

##########################################
# User Agents (Browsers)                 #
##########################################
 
# NOTE: numbers indicate the search order. This is very important since
# some words may be found in more than one browser description. (MSIE is
# presented as "Mozilla/4.0 (Compatible; MSIE 4.01; ...")
#
# for example, the "explorer=MSIE" tag indicates that the XSL stylesheet
# associated to the media type "explorer" should be mapped to those 
# browsers that have the string "MSIE" in their "user-Agent" HTTP header.
 
browser.0 = explorer=MSIE
browser.1 = opera=Opera
browser.2 = lynx=Lynx
browser.3 = java=Java
browser.4 = wap=Nokia
browser.5 = wap=UP
browser.6 = netscape=Mozilla

The keywords after the first equals sign are the items to take note of: explorer, lynx, java, and netscape, for example, all differentiate between different user-agents, the codes the browsers send with requests for URLs. As an example of applying stylesheets based on this property, let's create a sample XSL stylesheet to apply when the client accesses our XML table of contents document with Internet Explorer. Copy our original stylesheet, JavaXML.html.xsl, to JavaXML.explorer-html.xsl. Then make the modifications shown in Example 9-3.

Example 9-3: Internet Explorer XSL Stylesheet

<?xml version="1.0"?>
 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/"
>
 
  <xsl:template match="JavaXML:Book">
    <html>
      <head>
        <title>
          <xsl:value-of select="JavaXML:Title" /> (Explorer Version)
        </title>
      </head>
      <body>
        <xsl:apply-templates select="*[not(self::JavaXML:Title)]" />
      </body>
    </html>
  </xsl:template>
 
  <xsl:template match="JavaXML:Contents">
    <center>
     <h2>Table of Contents (Explorer Version)</h2>
     <small>
       Try <a href="http://www.netscape.com">Netscape</a> today!
     </small>
    </center>
...

While this is a trivial example, dynamic HTML could be inserted for Internet Explorer 5.0, and standard HTML could be used for Netscape Navigator, which has less DHTML support. With this in place, we need to let our XML document know that if the media type (or user-agent) matches up with the explorer type defined in the properties file, a different XSL stylesheet should be used. The additional processing instruction shown in Example 9-4 handles this, and can be added to the contents.xml file.

Example 9-4: XML Document with Multiple Stylesheets Based on Media Type

<?xml version="1.0"?>
<?xml-stylesheet href="XSL\JavaXML.html.xsl" type="text/xsl"?>
<?xml-stylesheet href="XSL\JavaXML.explorer-html.xsl" type="text/xsl"
                 media="explorer"?>
<?xml-stylesheet href="XSL\JavaXML.wml.xsl" type="text/xsl" 
                 media="wap"?>
<?cocoon-process type="xslt"?>
...

Accessing the XML in your Netscape browser yields the same results as before; however, if you access the page in Internet Explorer, you will see that the document has been transformed with the alternate stylesheet, and looks like Figure 9-4.

Figure 9-4. Internet Explorer version of generated HTML

 

WAP and WML

One of the real powers in this dynamic application of stylesheets lies in the use of wireless devices. Remember our properties file?

browser.0 = explorer=MSIE
browser.1 = opera=Opera
browser.2 = lynx=Lynx
browser.3 = java=Java
browser.4 = wap=Nokia
browser.5 = wap=UP
browser.6 = netscape=Mozilla

The two highlighted entries detect that a wireless agent, such as an Internet-capable phone, is being used to access content. Just as Cocoon detected whether the incoming web browser was Internet Explorer or Netscape, responding with the correct stylesheet, a WAP device can be handled by yet another stylesheet. So far we have looked at the line that specifies a stylesheet to use for WAP media in our contents.xml file without paying it much attention:

<?xml-stylesheet href="XSL\JavaXML.html.xsl" type="text/xsl"?>
<?xml-stylesheet href="XSL\JavaXML.explorer-html.xsl" type="text/xsl"
                 media="explorer"?>
<?xml-stylesheet href="XSL\JavaXML.wml.xsl" type="text/xsl" 
                 media="wap"?>
<?cocoon-process type="xslt"?>

Now we take a look at this in more detail. When building a stylesheet for a WAP device, the Wireless Markup Language (WML) is typically used. This is a variant on HTML, but has a slightly different method of representing different pages. When a wireless device requests a URL, the returned response must be within a wml element. Within that root element, several cards can be defined, each through the WML card element. The device downloads multiple cards at one time (often referred to as a deck) so that it does not have to go back to the server for the additional screens. Example 9-5 shows a simple XML page using these constructs.

Example 9-5: A Simple WML Page

<wml>
 <card id="index" title="Home Page">
  <p align="left">
   <i>Main Menu</i><br />
   <a href="#title">Title Page</a><br />
   <a href="#myPage">My Page</a><br />
  <p>
 </card>
 
 <card id="title" title="My Title Page">
  Welcome to my Title Page!<br />
  So happy to see you.
 </card>
 
 <card id="myPage" title="Hello World">
  <p align="center">
   Hello World!
  </p>
 </card>
</wml>

This simple example would serve requests with a menu, and two screens that could be accessed from links within that menu. The complete WML 1.1 specification is available online at http://updev.phone.com/dev/ts/ by signing up for a free membership to phone.com's developer website, located at http://updev.phone.com. Additionally, the UP.SDK can be downloaded; this is a software emulation of a wireless device that allows testing of your WML pages. With this software, we can develop an XSL stylesheet to output WML for WAP devices, and test the results by pointing our UP.SDK browser to http://<hostname>:<port>/contents.xml.

Because phone displays are much smaller, we only want to show a subset of the information in our XML table of contents. Example 9-6 is an XSL stylesheet that outputs three cards in WML. The first is a menu with links to the other two cards. The second card generates a table of contents listing from our contents.xml document. The third card is a simple copyright screen. This stylesheet can be saved as JavaXML.wml.xsl in the XSL/ subdirectory of your web server's document root.

Example 9-6: XSL Stylesheet to Output WML from XML Table of Contents

<?xml version="1.0"?>
 
<xsl:stylesheet version="1.0"                 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"                
                xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/"
                exclude-result-prefixes="JavaXML"
>
 
 <xsl:template match="JavaXML:Book">
  <xsl:processing-instruction name="cocoon-format">
   type="text/wml"
  </xsl:processing-instruction>
 
  <wml>
   <card id="index" title="{JavaXML:Title}">
    <p align="center">
     <i><xsl:value-of select="JavaXML:Title" /></i><br />
     <a href="#contents">Contents</a><br/>
     <a href="#copyright">Copyright</a><br/>
    </p>
   </card>
  
   <xsl:apply-templates select="JavaXML:Contents" />
 
   <card id="copyright" title="Copyright">
    <p align="center">
     Copyright 2000, O&apos;Reilly &amp; Associates
    </p>
   </card>
  </wml>
 </xsl:template>
 
 <xsl:template match="JavaXML:Contents">
  <card id="contents" title="Contents">
   <p align="center">
    <i>Contents</i><br />
    <xsl:for-each select="JavaXML:Chapter">
     <xsl:number value="position(  )" format="1: " />
     <xsl:value-of select="JavaXML:Heading" /><br />
    </xsl:for-each>
   </p>
  </card>
 </xsl:template>
 
</xsl:stylesheet>

Other than the WML tags, most of this example should look familiar. A new XSL function is introduced, position( ), and a new XSL element, xsl:number, displays it. This adds output that indicates the position in the xsl:for-each loop each element is at; the format attribute allows the specification of the output format. In our case, we want output similar to this:

1: Introduction
2: Creating XML
...

We also added a processing instruction for Cocoon, with the target specified as cocoon-format. The data sent, type="text/wml", instructs Cocoon to output this stylesheet with a content header specifying the output is text/wml (instead of the normal text/html or text/plain). The last new construct is an important one, and is seen as an attribute added to the root element of the stylesheet:

<xsl:stylesheet version="1.0"                 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"                
                xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/"
                exclude-result-prefixes="JavaXML"
>

By default, any XML namespace declarations other than the XSL namespace are added to the root element of the transformation output. In our example, the root element of our transformed output, wml, would have the JavaXML namespace declaration added to it:

<wml xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/">
...
</wml>

This would cause a WAP browser to report an error, as xmlns:JavaXML is not an allowed attribute for the wml element. The browser is not as forgiving as an HTML browser, and the rest of our content would not be shown. However, we must declare the namespace so our XSL stylesheet can handle template matching for the input document, which does use the JavaXML namespace. To handle this problem, XSL allows the attribute exclude-result-prefixes to be added to the xsl:stylesheet element. The namespace prefix specified to this attribute will not be added to the transformed output, which is exactly what we want. Our output would now look like this:

<wml>
...
</wml>

This is understood perfectly by a WAP browser. If you've downloaded the UP.SDK browser, you can point it to our XML table of contents, and see the results. Figure 9-5 shows the main menu that results from the transformation using our WML stylesheet when a WAP device requests our contents.xml file through Cocoon.

Figure 9-5. WML main menu

 

Figure 9-6 shows the generated table of contents, accessed by clicking the "Link" button when the "Contents" link is indicated in the display.

Figure 9-6. WML table of contents

 

For more information on WML and WAP, visit http://www.phone.com and http://www.wapforum.org ; both sites have extensive online resources for wireless device development.

By now, you should have a pretty good idea of the variety of output that can be created with Cocoon. With a minimal amount of effort and an extra stylesheet, the same XML document can be served in multiple formats to multiple types of clients; this is one of the reasons the web publishing framework is such a powerful tool. Without XML and a framework like this, separate sites would have to be created for each type of client. Now that you have seen how flexible the generation of output is when using Cocoon, we move on to looking at how Cocoon provides technology that allows for dynamic creation and customization of the input to these transformations.

XSP

XSP stands for Extensible Server Pages, and is perhaps the most important development coming out of the Cocoon project. Certainly you, Constant Reader, are familiar with Java Server Pages ( JSP). JSP (in a nutshell) allow tags and inline Java code to be inserted into an otherwise normal HTML page, and then when the JSP page is requested, the resulting code is executed and the results are inserted right into the output HTML.[4] This has taken the Java and ASP worlds by storm, ostensibly simplifying server-side Java programming and allowing a separation of output and logic. However, there are still some significant problems. First, JSP does not really provide a separation of content and presentation. This is the same problem we have been talking about time and time again; changes to a banner, the color of a font, and text sizes require the JSP (with the inline Java and JavaBean references) to be modified. It also mingles content (pure data) with presentation in the same way static HTML does. Second, there is no ability to transform the JSP into any other format, or use it across applications, because the JSP specification is designed primarily for delivery of output.

XSP remedies both these problems. First, XSP is, at its heart, simply XML. Take a look at the sample XSP page in Example 9-7.

Example 9-7: Sample XSP Page

<?xml version="1.0"?>
<?cocoon-process type="xsp"?>
<?cocoon-process type="xslt"?>
<?xml-stylesheet href="myStylesheet.xsl" type="text/xsl"?>
 
<xsp:page language="java" 
          xmlns:xsp="http://www.apache.org/1999/XSP/Core"
>
  
 <xsp:logic>
  private static int numHits = 0; 
  
  private synchronized int getNumHits(  ) { 
   return ++numHits; 
  }
 </xsp:logic>
 
 <page>
  <title>Hit Counter</title>
  
  <p>I've been requested <xsp:expr>getNumHits(  )</xsp:expr> times.</p>
 </page>
</xsp:page>

All XML conventions are followed; for now, think of the xsp:logic element contents as "off-limits" to the XML parser; we'll discuss that later. Other than that, the entire document is XML with some new elements. In fact, it references an XSL stylesheet that has nothing remarkable about it at all, as seen in Example 9-8.

Example 9-8: XSL Stylesheet for Example 9-7

<?xml version="1.0"?>
 
<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
>
 
  <xsl:template match="page">
    <xsl:processing-instruction name="cocoon-format">
      type="text/html"
    </xsl:processing-instruction>
    <html>
      <head>
        <title><xsl:value-of select="title"/></title>
      </head>
      <body>
        <xsl:apply-templates select="*[not(self::title)]" />
      </body>
    </html>
  </xsl:template>
 
  <xsl:template match="p">
    <p align="center">
      <xsl:apply-templates />
    </p>
  </xsl:template>
 
</xsl:stylesheet>

Thus, XSP easily handles the first major problem of JSP: it allows the separation of content from presentation. This separation allows developers to handle content generation (as the XSP page can be generated from a servlet or other Java code as well as being static), while XML and XSL authors can handle presentation and styling through modification of the XSL stylesheet applied to the XSP page. Just as easily, XSP solves the other significant deficiency of JSP: because XSP processing occurs before any stylesheets are applied, the resultant XML document can be transformed into any other format. This maintains all the advantages of XML, as the XSP page can be transferred between applications as well as being used just for presentation.

Creating an XSP Page

Now that you have had a taste of XSP, let's build our own XSP page. For this example, let's continue looking at the XML documents we have already created. We revisit the XML document we constructed earlier. This document represents a portion of the first chapter of this book, and was transformed into a PDF document. Instead of simply using this document for display, let's assume that the author (me!) wants to let his editor view the document as it is being written. However, in addition to the text of the book, the editor should be able to see comments from the author that the public should not see: for example, questions about style and formatting. First, let's add the comment to the chapterOne.xml file we built earlier:

...
 <contents>
  <chapter id="chapterOne">
   <title>Chapter 1: Introduction</title>
      
   <paragraph>XML.  These three letters have brought shivers to 
   almost every developer in the world today at some point in the
   last two years.  While those shivers were often fear at another
   acronym to memorize, excitement at the promise of a new technology,
   or annoyance at another source of confusion for today's 
   developer, they were shivers all the same.  Surprisingly, almost every
   type of response was well merited with regard to XML.  It is another 
   acronym to memorize, and in fact brings with it a dizzying array of 
   companions: XSL, XSLT, PI, DTD, XHTML, and more.  It also brings with 
   it a huge promise-what Java did for portability of code, XML claims 
   to do for portability of data.  Sun has even been touting the 
   rather ambitious slogan "Java + XML = Portable Code + Portable 
   Data" in recent months.  And yes, XML does bring with it a 
   significant amount of confusion.  We will seek to unravel and 
   demystify XML, without being so abstract and general as to be 
   useless, and without diving in so deeply that this becomes just 
   another droll specification to wade through.  This 
   is a book for you, the Java developer, who wants to understand the 
   hype and use the tools that XML brings to the table.</paragraph>
 
   <authorComment>Is the formatting of this first paragraph OK?  I 
   wonder if we should break this into two separate paragraphs.  Let 
   me know what you think, Mike.</authorComment>
 
   <paragraph>Today's web application now faces a wealth of problems
   that were not even considered ten years ago.  Systems that are 
   distributed across thousands of miles must perform quickly and 
   flawlessly.  Data from heterogeneous systems, databases, directory 
   services, and applications must be transferred without a single 
   decimal place being lost.  Applications must be able to communicate 
   not only with other business components, but other business systems 
   altogether, often across companies as well as technologies.  Clients 
   are no longer limited to thick clients, but can be web browsers that 
   support HTML, mobile phones that support Wireless Application 
   Protocol (WAP), or handheld organizers with entirely different markup 
   languages altogether. Data, and the transformation of that data, has 
   become the crucial centerpiece of every application being developed 
   today.</paragraph>
  </chapter>
 
 </contents>
</book>

With this comment now in our XML document, let's add a corresponding entry into our XSL stylesheet, JavaXML.fo.xsl:

<xsl:template match="paragraph">
  <fo:block font-size="12pt" space-before.optimum="12pt" 
            text-align="justified">
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>
 
<xsl:template match="authorComment">
  <fo:block font-size="10pt" font-style="italic" color="blue" 
            space-before.optimum="12pt" 
            text-align="justified">
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>

With this new entry, the comments will appear slightly smaller than the rest of the text, italicized, and in blue. Now let's turn our XML document into an XSP page (as in Example 9-9) by adding the needed processing instructions for Cocoon, and surrounding the elements within a new root element, xsp:page.

Example 9-9: XSP Version of Example 9-1

<?xml version="1.0"?>
 
<?cocoon-process type="xsp"?>
<?cocoon-process type="xslt"?>
<?xml-stylesheet href="XSL/JavaXML.fo.xsl" type="text/xsl"?>
 
<xsp:page 
  language="java" 
  xmlns:xsp="http://www.apache.org/1999/XSP/Core"
>
<book>
  <cover>
    <title>Java and XML</title>
    <author>Brett McLaughlin</author>
  </cover>
 
  <!-- Content of Chapter -->
 
</book>
</xsp:page>

Before adding XSP logic to determine whether or not to show the comment, let's build a simple HTML page letting the viewer select whether he or she is the book's editor. In a real application, this could be a page that handles authentication and determines a user's role; for our example, it lets the user select if they are the author, the editor, or just a curious reader, and enter a password for verification. An HTML page that does this is shown in Example 9-10. You can save this file as entry.html in your web server's document root.

Example 9-10: HTML Frontend for User to Select a "Role"

<html>
 <head>
  <title>Welcome to the Java and XML Book in Progress</title>
 </head>
 
 <body>
  <h1 align="center"><i>Java and XML</i> Book in Progress</h2>
  <center>
   <form action="/chapterOne.xml" method="POST">
    Select your role:
    <select name="userRole">
     <option value="author">I'm the Author</option>
     <option value="editor">I'm the Editor</option>
     <option value="reader">I'm a Reader</option>
    </select>
    <br />
    Enter your password:
    <input type="password" name="password" size="8" />
    <br /><br />
    <input type="submit" value="Take me to the Book!" />
   </form>
  </center>
 </body>
</html>

Also notice that we submit the HTML form directly to our XSP page. In this example, our XSP acts similarly to a servlet. We want it to read the request parameters, determine what user role was selected, authenticate that role using the password supplied, and finally determine whether we should show the comment. To begin, let's define a boolean variable; this variable will hold the result of comparing the request parameters to see if the user is an author or editor and supplied a correct password. We then check the value of that variable, and if it is true, display the authorComment element:

<xsp:page 
  language="java" 
  xmlns:xsp="http://www.apache.org/1999/XSP/Core"
>
 
<book>
  <cover>
    <title>Java and XML</title>
    <author>Brett McLaughlin</author>
  </cover>
...
   is a book for you, the Java developer, who wants to understand the 
   hype and use the tools that XML brings to the table.</paragraph>
 
   <xsp:logic>
    boolean authorOrEditor = false;
 
    // Perform logic to see if user is an author or editor
 
    if (authorOrEditor) {
      <xsp:content>
       <authorComment>Is the formatting of this first paragraph OK?  I 
       wonder if we should break this into two separate paragraphs.  Let 
       me know what you think, Mike.</authorComment>
      </xsp:content>
    }
   </xsp:logic>
 
   <paragraph>Today&apos;s web application now faces a wealth of problems
   that were not even considered ten years ago.  Systems that are
...

This shouldn't look too odd to you; other than the XSP-specific tags, we're just defining a variable and checking its value. If the variable evaluates to true, the authorComment element is added to the XSP page's output; otherwise, the element is not included in the output. One interesting thing to note is that we surround the actual XML document output within the xsp:logic block with an xsp:content element (which in turn is within the outer xsp:page element). This ensures that the XSP processor does not try to interpret any elements or text within the block as XSP structures. This again is an improvement to JSP; the same code in JSP might look like this:

<%
 if (authorOrEditor) {
%>
  <authorComment> Is the formatting of this first paragraph OK?  I 
  wonder if we should break this into two separate paragraphs.  Let 
  me know what you think, Mike.</authorComment>
<%
 }
%>

This is not very structured, as the JSP block ends before the authorComment element begins; then a new block is appended after the element, which closes the brackets opened in the first JSP block. It is very easy to mismatch coding structures or forget to add matching JSP blocks; the XSP paradigm forces every open element to be closed (standard XML well-formedness) and one block of code is matched with one element.

With our logical structures in place, we just need to interpret the request parameters. We use the built-in XSP variable request, which mimics the Servlet HttpServletRequest object. The following code additions read the value of the userRole and password request parameters (if they exist); the value is then compared with the roles that can see the comments (author and editor). If a match occurs, the password is checked as well. If the password matches the key for the supplied role, the boolean variable is set to true, and the authorComments element is part of the XML output:

<xsp:logic>
 boolean authorOrEditor = false;
 
 // Perform logic to see if user is an author or editor
 <![CDATA[
 String[] roleValues = request.getParameterValues("userRole");
 String[] passwordValues = request.getParameterValues("password");
 if ((roleValues != null) && (passwordValues != null)) {
   String userRole = roleValues[0];
   String password = passwordValues[0];
   if (userRole.equals("author") && password.equals("brett")) {
     authorOrEditor = true;
   } else
   if (userRole.equals("editor") && password.equals("mike")) {
     authorOrEditor = true;
   }
 ]]>
 }
 
 if (authorOrEditor) {
   <xsp:content>
    <authorComment>Is the formatting of this first paragraph OK?  I 
    wonder if we should break this into two separate paragraphs. Let 
    me know what you think, Mike.</authorComment>
   </xsp:content>
 }
</xsp:logic>

Notice that we enclose a good bit of this logic within a CDATA tag. Remember that XSP is still evaluated as XML, and must follow the rules of an XML document; but the double quotes and ampersands we use in the Java fragments are not allowed in XML documents; instead of escaping these characters, and getting a very strange XSP fragment, we use the CDATA tag so that we can write standard Java code. Without this, we would have to code as follows:

<xsp:logic>
 boolean authorOrEditor = false;
 
 String[] roleValues = 
   request.getParameterValues(&quot;userRole&quot;);
 String[] passwordValues = 
   request.getParameterValues(&quot;password&quot;);
 if ((roleValues != null) &amp;&amp; 
     (passwordValues != null)) {
   String userRole = roleValues[0];
   String password = passwordValues[0];
   if (userRole.equals("author") &amp;&amp; 
       password.equals("brett")) {
     authorOrEditor = true;
   } else
   if (userRole.equals("editor") &amp;&amp; 
       password.equals("mike")) {
     authorOrEditor = true;
   }
 }
...
</xsp:logic>

You can now test out our entry page and the resultant PDF generated from the XML. You should get output similar to Figure 9-7 if you direct your web browser to http://<hostname>:<port>/entry.html.

Figure 9-7. Entry HTML page

 

Select the role of author, and use the password "brett"; otherwise use the editor role with the password "mike." Either case gives you the PDF output shown in Figure 9-8.

Figure 9-8. Generated PDF with comments showing

 

The one thing we have not yet done is isolate our logic from our content. Just as JSP allows inclusion of JavaBeans to abstract the content and presentation from the logic of an application component, XSP allows tag libraries to be created. These tag libraries can then allow XML tags to trigger the matching code within a tag library.

Using XSP Tag Libraries

In addition to showing comments based on the user, we should indicate that the chapter is in a draft state; additionally, the current date can be shown to indicate the date of the draft (the intention would be that the date be frozen when the chapter is considered complete). Instead of adding inline Java tags to load the current date, we can create a custom tag library for this purpose. While we are at it, let's look at creating an XSP element that takes in the chapter number and title and formats the complete title. This function will handle the insertion of the draft date we have been talking about. To do this, we first need to create a tag library that is available to our XSP page. Much of the tag library is based on an XSL stylesheet. We can start with the skeleton in Example 9-11, which passes anything it receives through as output. Save this skeleton as JavaXML.xsp.xsl in the XSL/ subdirectory. Be sure to include the JavaXML namespace declaration, as we will use it to match elements within that namespace used in our XSP pages.

Example 9-11: XSP Logicsheet

<?xml version="1.0"?>
 
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xsp="http://www.apache.org/1999/XSP/Core"
  xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/"
>
  <xsl:template match="xsp:page">
    <xsp:page>
      <xsl:copy>
        <xsl:apply-templates select="@*"/>
      </xsl:copy>
 
      <xsl:apply-templates/>
    </xsp:page>
  </xsl:template>
 
  <xsl:template match="@*|*|text()|processing-instruction(  )">
    <xsl:copy>
      <xsl:apply-templates 
           select="@*|*|text()|processing-instruction(  )"/>
    </xsl:copy>
  </xsl:template>
 
</xsl:stylesheet>

By matching the xsp:page tag, we ensure that all elements are matched and handled within this stylesheet, or logicsheet in XSP parlance. We can now add Java methods for the templates within this logicsheet to call:

<xsl:template match="xsp:page">
  <xsp:page>
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
    </xsl:copy>
 
    <xsp:structure>
      <xsp:include>java.util.Date</xsp:include>
      <xsp:include>java.text.SimpleDateFormat</xsp:include>
    </xsp:structure>
 
    <xsp:logic>
      private String getDraftDate(  ) {
        return (new SimpleDateFormat("MM/dd/yyyy"))
          .format(new Date(  ));
      }
 
      private String getTitle(int chapterNum, String chapterTitle) {
        return "Chapter " + chapterNum + ": " + chapterTitle;
      }
    </xsp:logic>
 
    <xsl:apply-templates/>
  </xsp:page>
</xsl:template>

Several new XSP elements are introduced here. First, xsp:structure is used to surround several xsp:include statements. These work just like their Java counterpart, include, by making the specified Java classes available for use by their unqualified name (rather than the complete package name). Once these are available, we define and implement two methods: one that creates a chapter title from the chapter number and textual title, and one that returns the current date as a formatted String. These methods are available to any elements within this logicsheet.

We now need to define the element that specifies when an XSP result should replace an XML element. We have already defined the JavaXML namespace in the document root element, so we use that as the namespace for our tag library elements. Add the following template:

<!-- Create formatted title -->
<xsl:template match="JavaXML:draftTitle">
  <xsp:expr>getTitle(<xsl:value-of select="@chapterNum" />,
                     "<xsl:value-of select="@chapterTitle" />")
  </xsp:expr> - <xsp:expr>getDraftDate(  )</xsp:expr>
</xsl:template>
 
<xsl:template match="@*|*|text()|processing-instruction(  )">
  <xsl:copy>
    <xsl:apply-templates 
         select="@*|*|text()|processing-instruction(  )"/>
  </xsl:copy>
</xsl:template>

When a document with this tag library uses the element JavaXML:draftTitle, the result of the method getTitle( ) will be prepended to a dash (-), and then the returned value of the getDraftDate( ) method will be appended to that result. The JavaXML:draftTitle element also expects two attributes to be declared: the chapter number and the textual title of the chapter. We signify to the XSP processor that we are calling a defined method by enclosing the method call within a set of <xsp:expr> tags. To indicate that the second argument (the chapter title) is a String, we enclose it within quotes. Since the chapter number should be treated as an int, it is left without quotation marks.

Once you have completed the XSP logicsheet (available online at the book's web site as well), you need to make it accessible to Cocoon. This can be done one of two ways: the first is to specify the location of the file as a URI, which allows the servlet engine (and therefore Cocoon) to locate the logicsheet. For example, to add our XSP logicsheet to Cocoon's set of resources through its URI, you could add the following line to your cocoon.properties file on a Unix-based system:

processor.xsp.library.context.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/context.xsl
processor.xsp.library.cookie.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/cookie.xsl
processor.xsp.library.global.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/global.xsl
processor.xsp.library.request.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/request.xsl
processor.xsp.library.response.java =
  resource://org/apache/cocoon/processor/xsp/library/java/response.xsl
processor.xsp.library.session.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/session.xsl
processor.xsp.library.util.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/util.xsl
 
processor.xsp.library.JavaXML.java = 
  file:///usr/local/jakarta-tomcat/webapps/ROOT/XSL/JavaXML.xsp.xsl

For Windows systems, this would be:

processor.xsp.library.context.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/context.xsl
processor.xsp.library.cookie.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/cookie.xsl
processor.xsp.library.global.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/global.xsl
processor.xsp.library.request.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/request.xsl
processor.xsp.library.response.java =
  resource://org/apache/cocoon/processor/xsp/library/java/response.xsl
processor.xsp.library.session.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/session.xsl
processor.xsp.library.util.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/util.xsl
 
processor.xsp.library.JavaXML.java = 
  file:///C:/java/jakarta-tomcat/webapps/ROOT/XSL/JavaXML.xsp.xsl

While this is handy for testing, it is not a very good solution for uncoupling your logicsheets from the servlet engine, and also adds quite a bit of maintenance overhead when adding new logicsheets: a new line would have to be added to the Cocoon properties file for new logicsheets to be available.[5] An alternative method for loading logicsheets is to allow specification of a resource in the servlet engine's classpath. This allows all of your custom logicsheets to be added to a jar file, and that jar file to be added to the servlet engine classpath. In addition, new logicsheets can be put within the jar file, providing a central location for storing your custom XSP logicsheets. From the XSL/ subdirectory in your web server's document root, perform the following command to create a jar file that contains our logicsheet:

jar cvf logicsheets.jar JavaXML.xsp.xsl

Move the created logicsheets.jar archive into your <TOMCAT_HOME>/lib/ directory with the other Cocoon libraries. Now we need to add this library to Tomcat's class path; edit the tomcat.sh or tomcat.bat file, located in the <TOMCAT_HOME>/bin/ directory. In Unix, the edited file would look like this:

CLASSPATH=.
 
# Cocoon classes and libraries
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xerces_1_0_3.jar
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/xalan_0_20_0.jar
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/fop_0_12_1.jar
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/cocoon.jar
CLASSPATH=${CLASSPATH}:${TOMCAT_HOME}/lib/logicsheets.jar
 
for i in ${TOMCAT_HOME}/lib/* ; do
  CLASSPATH=${CLASSPATH}:$i
done
 
CLASSPATH=${CLASSPATH}:${JAVA_HOME}/lib/tools.jar

And on Windows:

set CLASSPATH=.
set CLASSPATH=%TOMCAT_HOME%\classes
 
rem Cocoon classes and libraries
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xerces_1_0_3.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xalan_1_0_0.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\fop_0_12_1.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\cocoon.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\logicsheets.jar
 
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\webserver.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\jasper.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\xml.jar
set CLASSPATH=%CLASSPATH%;%TOMCAT_HOME%\lib\servlet.jar
set CLASSPATH=%CLASSPATH%;%JAVA_HOME%\lib\tools.jar

With our logicsheet available, we can now let Cocoon know where to look for JavaXML namespace references within XSP pages. Edit the cocoon.properties file you earlier put in the <TOMCAT_HOME>/conf/ directory. Locate the section that lists the various Cocoon XSP resources, and add the new logicsheet reference:

processor.xsp.library.context.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/context.xsl
processor.xsp.library.cookie.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/cookie.xsl
processor.xsp.library.global.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/global.xsl
processor.xsp.library.request.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/request.xsl
processor.xsp.library.response.java =
  resource://org/apache/cocoon/processor/xsp/library/java/response.xsl
processor.xsp.library.session.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/session.xsl
processor.xsp.library.util.java = 
  resource://org/apache/cocoon/processor/xsp/library/java/util.xsl
 
processor.xsp.logicsheet.JavaXML.java = resource://JavaXML.xsp.xsl

Because our logicsheet is not nested within any subdirectories in the logicsheets.jar file, we simply use the name of the logicsheet as its resource path. Finally, you will need to restart the servlet engine. This will reload the cocoon.properties file, and the logicsheet will be available for use. As the Cocoon engine is used to handle requests, any XSP page that declares that it uses the JavaXML will have available to it the logicsheet specified as the JavaXML library. So our XSP page needs to add a namespace declaration for the JavaXML namespace:

<?xml version="1.0"?>
 
<?cocoon-process type="xsp"?>
<?cocoon-process type="xslt"?>
<?xml-stylesheet href="XSL/JavaXML.fo.xsl" type="text/xsl"?>
 
<xsp:page 
  language="java" 
  xmlns:xsp="http://www.apache.org/1999/XSP/Core"
  xmlns:JavaXML="http://www.oreilly.com/catalog/javaxml/"
>
<book>
...

With the tag library now available for use, we can finally add in the JavaXML:draftTitle element to our XML document, chapterOne.xml :

<contents>
  <chapter id="chapterOne">
    <title>
      <JavaXML:draftTitle chapterNum="1" 
                          chapterTitle="Introduction" 
      />
    </title>

We replace the hardcoded chapter title with the element defined in our tag library. This should generate the title with the chapter number, chapter title, and the date of the draft. Accessing this new version of our XSP page results in the output shown in Figure 9-9.

Figure 9-9. Output of XSP using a tag library

 

Certainly these are simple examples, and we have only scratched the surface of what XSP allows. Even this simple example allows the title to be converted to a different form when the chapter is complete, without modifying the content or presentation of the page, but only the XSP logicsheet. In the same way, XSP allows the creation of very strict contracts separating presentation from content from application logic. Adding server-side Java components such as Enterprise JavaBeans can bring business logic into the equation. Rather than using a less flexible solution like JSP that is coupled to HTML and a presentation format, using XSP allows a looser coupling of components and thus a better solution for application development. XSP also promises to be key in the upcoming version of Cocoon, Cocoon 2.0, which we look at now.

Cocoon 2.0 and Beyond

The next generation of Cocoon, Cocoon 2.0, promises to take the web publishing framework a gigantic leap forward. Cocoon 1.x, which is primarily based on XML being transformed via XSL, still has some serious limitations. First, it does not reduce the management costs of large sites significantly. While one XML document can be transformed into different client views, a significant number of documents will still exist. Generally, either long URIs (such as /content/publishing/books/javaxml/contents.xml ), a large number of virtual path mappings (/javaxml mapped to /content/publishing/books/javaxml ), or a combination of the two, result. In addition, a strict separation of presentation from content from logic is still difficult to accomplish, and even more difficult to manage.

Cocoon 2 focuses on enforcing the contracts between these different layers, therefore reducing management costs. XSP is a centerpiece in this design. In addition, the sitemap (which we look at in a moment) allows the distinction between XSP, XML, and static HTML pages to be hidden from the prying user. Advanced pre-compilation and memory considerations will also be introduced to make Cocoon 2 an even more significant advance over Cocoon 1.x than Cocoon 1.x was over a standard web server.

Servlet Engine Mappings

A significant change in Cocoon 2 is that it is no longer requires a simple mapping for XML documents. While this works well in the 1.x model, it still leaves management of non-XML documents completely up to the webmaster, possibly someone completely different from the person responsible for the XML documents. Cocoon 2 seeks to take over management of the entire web site. For this reason, the main Cocoon servlet (org.apache.cocoon.servlet.CocoonServlet in the 2.0 model) is generally mapped to a URI, such as /Cocoon. This could also be mapped to the root of the web server itself (simply "/" ) to completely control a site. The URL requested then follows the servlet mapping: http://myHost.com/Cocoon/myPage.xml or http://myHost.com/Cocoon/myDynamicPage.xsp, for example.

With this mapping in place, even static HTML documents can be grouped with XML documents. This allows the management of all files on the server to be handled by a central person or group. If HTML and XML documents must be mixed in a directory, no confusion needs to occur, and uniform URIs can be used. Cocoon 2 will happily serve HTML as well as any other document type; with a mapping from the root of a server to Cocoon, the web publishing framework actually becomes invisible to the client.

The Sitemap

Another important introduction to Cocoon 2 is the sitemap. In Cocoon, a sitemap provides a central location for administration of a web site. Cocoon uses this sitemap to decide how to process the request URIs it receives. For example, when Cocoon receives a request like http://myCocoonSite.com/Cocoon/javaxml/chapterOne.html, the Cocoon servlet dissects the request and determines that the actual URI requested is /javaxml/chapterOne.html. However, suppose that the file chapterOne.html should map not to a static HTML file, but to the transformation of an XML document (as in our earlier examples). The sitemap can handle this. Take a look at the sitemap shown in Example 9-12.

Example 9-12: Sample Cocoon 2.0 Sitemap

  <sitemap>
   <process match="/javaxml/*.html">
    <generator type="file" src="/docs/javaxml/*.xml"
    <filter type="xslt">
     <parameter name="stylesheet" value="/styles/JavaXML.html.xsl"/>
    </filter>
    <serializer type="html"/>
   </process>
 
   <process match="/javaxml/*.pdf">
    <generator type="file" src="/docs/javaxml/*.xml"
    <filter type="xslt">
     <parameter name="stylesheet" value="/styles/JavaXML.pdf.xsl"/>
    </filter>
    <serializer type="fop"/>
   </process>
  </sitemap>

WARNING: Although the sitemap DTD is being finalized as this book goes to production, changes could be introduced in beta-testing. Take the example sitemap as a flavor of what is to come in Cocoon 2 rather than a definitive sample.

Cocoon matches the URI /javaxml/chapterOne.html to the sitemap directive /javaxml/*.html. It then determines that this is an actual file, and the source for that file should be determined by using the mapping /docs/javaxml/*.xml, which translates to /docs/javaxml/chapterOne.xml (the filename we want to have transformed). The XSLT filter is then applied; the stylesheet to use, JavaXML.html.xsl, is also specified in the sitemap. The resulting transformation is then displayed to the user. In addition, the XML file could be an XSP file that is processed before being converted to XML and then styled.

This same process can render a PDF from the request http://myCocoonSite.com/Cocoon/javaxml/chapterOne.pdf, all with a few extra lines in the sitemap (shown above). This also means that the processing instructions in the individual XML documents can be completely removed, a significant change from Cocoon 1.x. First, uniform application of stylesheets and processing can occur based on a directory location. Simply creating XML and placing it in the /docs/javaxml/ directory in the example means the document can be accessed as HTML or PDF. It is also trivial to change the stylesheet used for all documents, something very difficult and tedious to do in Cocoon 1.x. Instead of making a change to each XML document, only the single line in the sitemap needs to be changed.

The Cocoon sitemap is still being developed, and there will probably be quite a few additional enhancements and changes to its format and structure by the time Cocoon 2.0 goes final. To get involved, join the mailing lists at cocoon-users@xml.apache.org and cocoon-dev@xml.apache.org. The Apache XML project at http://xml.apache.org has details about how to get involved with these lists and the Cocoon project.

Producers and Processors

One final improvement that Cocoon 2 will include is precompiled and event-based producers and processors. In Cocoon, a producer handles the transformation of a request URI into an XML document stream. A processor then takes an input stream (currently the XML document in a DOM tree) into output readable by the client. We have not covered producers and processors in the Cocoon 1.x model because they are going to drastically change in the Cocoon 2.0 model; any producers and processors currently being used will most likely be useless and have to be rewritten in Cocoon 2.0.

Cocoon 2 moves from using DOM for these structures to the more event-based SAX (or even JDOM!), wrapped within a DOM structure. As a producer had to generate an XML document in memory, the corresponding DOM structure could get extremely large. This eventually drained system resources, particularly when performing complex tasks such as large transformations or handling formatting objects (PDF generation). For these reasons, DOM will be a simple wrapper around SAX-based events in Cocoon 2, allowing producers and processors to be very slim and efficient.

In addition, producers and processors will be pre-complied versions of other formats. For example, XSL stylesheets can be precompiled into processors, and XSP pages can be precompiled into producers. This further increases performance while removing load from the client. These and other changes continue to use a component model, allowing Cocoon to be a very flexible, very pluggable framework. Keep up on the latest changes by monitoring the Cocoon web site, and look for Cocoon 2 in late 2000.

What's Next?

In the next chapter, we take a look at a technology that allows XML to be used as a data format in an important request and response model: XML-RPC. XML Remote Procedure Calls allow clients in a distributed system to request that tasks be executed on a server (or servers) on another portion of the network. Until recently, RPC has declined in popularity, mostly due to the surge of RMI-based technologies in the Java space (most notably, EJB). However, with XML as a data format, XML-RPC is a new solution for many problems that could not be solved cleanly or efficiently without RPC. We take a look at XML-RPC next, and in particular, at the Java libraries available.


1. This is the same FOP (Formatting Objects Processor) we looked at in Chapter 6, Transforming XML, when discussing converting XML to non-textual formats.

2. Stylebook is a project that handles generation of very complex documents, including HTML, and is used to generate the Cocoon documentation and web site. The Stylebook project is set to integrate with Cocoon in Cocoon 2.0.

3. Although the 3.1 build is used in these examples, Cocoon only requires a 2.x version of the servlet API. In addition, configuration options in Tomcat should not be subject to change with revisions of the engine; in other words, the instructions within this chapter should apply to any 3.x version of Tomcat.

4. This is a drastic oversimplification; the JSP is actually precompiled into a servlet, and a PrintWriteractually handles output. For more information on JSP, refer to Java Servlet Programming, by Jason Hunter (O'Reilly & Associates).

5. Additionally, there are some rare occurrences where the 1.7.x version of the Cocoon engine has problems loading a logicsheet from a file:// reference. Using the classpath and resource:// alternative is a way to be sure you avoid these problems.

Back to: Java and XML


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com