XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Generating XML and HTML using XQuery
by Per Bothner | Pages: 1, 2, 3, 4, 5, 6

Running XQuery programs as Servlets

Java servlets are a popular framework for having a web server process an HTTP request. A JSP page is an HTML template that can contain embedded Java expressions and commands. A JSP-capable web server automatically translates (if needed) a JSP page into a Java servlet class. Such a server can be configured in various ways to select which servlet is executed for a particular HTTP request.

The photo-album application would be very difficult to write using JSP. However, it is quite easy to write using XQuery. There is at this time no standard for how web servers can cause XQuery programs to be executed in response to a web request, so we will look at how the Qexo implementation supports servlets. Most of the XQuery program remains unchanged. What is different is the top-level or main expression:

let $path := request-servlet-path(),
    $last-slash := last-index-of($path, "/"),
    $dir := substring($path, 1, $last-slash),
    $file := substring($path, $last-slash+1),
    $webapp-dir := servlet-context-realpath(),
    $xml-file := concat($webapp-dir, $dir, "index.xml"),
    $group := document($xml-file)/group
return
    if ($file = "index.html") then
        make-group-page($group)
    else
        let $group-date := $group/date,
            $group-name := $group/title,
            $pictures := $group//picture,
            $count := count($pictures),
            $style := if (last-index-of($file, "info.html") > 0) then "info"
                else if (last-index-of($file, "large.html") > 0) then "large"
                else "",
            $base-length := string-length($file) - string-length($style) - 5,
            $base := substring($file, 1, $base-length),
            $i := (for $j in 1 to $count return
                if ($base = string(item-at($pictures,$j)/@id)) then $j else ())
            return
                if (empty($i)) then
                    error-response(404, $file)
                else
                    let $picture := item-at($pictures,$i),
                        $id := string($picture/@id),
                        $prev :=
                            if ($i = 0) then ()
                            else item-at($pictures, $i - 1),
                        $next :=
                            if ($i >= $count) then ()
                            else item-at($pictures, $i + 1),
                        $date :=
                            if ($picture/date) then $picture/date
                            else $group-date
                    return
                        picture($picture, $group, string($picture/@id),
                            $prev, $next, $date, $style)

To get the request URL relative to the servlet context we use Qexo's request-servlet-path function. We extract the directory name and the file name using some string operations. (The last-index-of function is a Qexo function that returns the last position in the first argument of a substring matching the second argument, or 0 if the first argument does not contains the second argument.) Next we need to read the correct index.xml file. To find it, we use the Qexo function servlet-context-realpath,. It gives us the root directory of the current (pics) web application, to which we append the directory that we extracted from the request and the constant "index.xml". If the requested $file was "index.html", we pass the read $group to make-group-page as before and we're done. Otherwise things are a little complicated.

We need to find the correct <picture> element in the read document by searching for one that matches the incoming request. There are various ways of doing this, but there are a few concerns to be mindful of:

  • We need to find not just the current <picture> but also the previous and next pictures, so we can create links to them. Thus we need not just the requested <picture> element, but also its index in the sequence $pictures. We do that using the for-loop that sets the value of $i.

  • We want to emit some suitable error response if the request is for a <picture> that doesn't exist. We do that by testing empty($i) and calling the Qexo error-response function. This function takes a HTTP error code and a message and creates an appropriate error value that gets sent back to the browser.

  • It is more efficient if the HTML page generated by the picture function can be written directly to the servlet output buffer, as opposed to creating a temporary document value and writing that out. To make it easier for an XQuery compiler to avoid unnecessary copying, we call the picture function so its output directly becomes the output of the top-level expression, without first storing it in a temporary variable.

The call to document in the picture function must be similarly modified:

define function picture($picture, $group, $name, $prev, $next, $date, $style) {
  ...
  if ($style = "info") then (
    make-thumbnail($picture),
   (let $path := request-servlet-path(),
         $last-slash := last-index-of($path, "/"),
         $dir := substring($path, 1, $last-slash) return
    document(concat(servlet-context-realpath(),$dir,$name,"-info.txt"))),
  ...
}

Otherwise, the servlet version is same as the batch version.

Installing the Servlet under the Tomcat Server

A number of web servers can run servlets. The servlet specification describes how a web application consists of some number of servlets, static pages, and a web.xml deployment descriptor. We will show how you can create a application running under the Tomcat server from the photo album program. Tomcat is an open-source web server written in Java and released by the Apache Foundation's Jakarta project. These instructions assume you have version 4.0 of Tomcat installed and that the value of $CATALINA_HOME, where Tomcat is installed, is /opt/Tomcat. We also assume a Posix-compatible environment, including an sh-style shell such as GNU bash. (If you use Microsoft Windows you can get these as part of the Cygwin environment.) If you have a different environment, or need information beyond these very basic instructions, check out the Tomcat documentation.

We assume you're using the Qexo implementation of XQuery. The Qexo implementation classes are included in the kawa-N.M.jar file containing the Kawa compiled classes, where N.M is the Kawa version number. You will need to install this JAR file somewhere where Tomcat can find it. For example, you can install it in $CATALINA_HOME/lib/kawa-N.M.jar.

Create the actual pics web application. You need to create the directories $CATALINA_HOME/webapps/pics, $CATALINA_HOME/webapps/pics/WEB-INF, and $CATALINA_HOME/webapps/pics/WEB-INF/classes. Under Unix, Linux, or Windows, do the following:

mkdir $CATALINA_HOME/webapps/pics
mkdir $CATALINA_HOME/webapps/pics/WEB-INF
mkdir $CATALINA_HOME/webapps/pics/WEB-INF/classes

You next need to compile the pictures XQuery program to a servlet:

kawa --xquery --servlet -C pictures.xql

This creates a pictures.class file, which you need to install in the $CATALINA_HOME/webapps/pics/WEB-INF/classes directory, so Tomcat can find it:

cp pictures.class $CATALINA_HOME/webapps/pics/WEB-INF/classes/pictures.class

Next you need to write the web.xml deployment descriptor. This is an optional file that describes the web application and lives in the $CATALINA_HOME/webapps/pics/WEB-INF directory. The following is sufficient for the pics application:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <display-name>Web Photo Album</display-name>

  <servlet>
    <servlet-name>pictures</servlet-name>
    <servlet-class>pictures</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>pictures</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
</web-app>

The <display-name> element is just a human-readable description of the web application. Each <servlet> element names a servlet and gives the fully-qualified name of the Java class used to implement it. (In this example, they're the same.) The <servlet-mapping> element specifies which URLs (within the pics web application) cause the named servlet to be called; in this case, all requests that end in the .html extension.

Finally, we need to create some content. We can take the *.jpg files and the index.xml file from the earlier NorwayHighlights and copy them into the web application's directory $CATALINA_HOME/webapps/pics.

If Tomcat isn't already running, start it now:

JAVA_HOME=$(JAVA_HOME)
CATALINA_HOME=$(CATALINA_HOME)
export JAVA_HOME CATALINA_HOME
$(CATALINA_HOME)/bin/startup.sh start

After you've started Tomcat, it by default listens to port 8080. To view the NorwayHighlights album, point your browser at http://localhost:8080/pics/NorwayHighlights/index.html. When Tomcat receives this request, it will invoke the pictures servlet, and the call request-servlet-path() will return /NorwayHighlights/index.html. The $file variable will get set to index.html, so make-group-page is called. If you click a link, the browser sends back a new request, which the servlet handles. Any .jpg image files that might be requested via an <img> or <a> tag, such as http://localhost:8080/pics/NorwayHighlights/Skjolden1.jpg, will not be handled by the servlet. Instead, Tomcat handles those using its default rules, and looks for matching files relative to the web application directory, such as $CATALINA_HOME/webapps/pics.

Comparison with JavaServer Pages

We can summarize the advantages of XQuery versus JSP and similar template systems as follows:

  • Better integration than JSP's HTML+Java. You can use the same programming language for both the presentation and the logic.

  • XQuery is a higher-level language compared to Java, with powerful features for manipulating XML.

  • XQuery is a true expression language; values can be structured XML that you can pass to functions. You don't need to clutter your program with print invocations.

  • Qexo can run XQuery programs in different modes, even expressions typed at a command prompt. This helps development and testing.

  • Errors (such as stack traces) refer to the line numbers in the XQuery source, as opposed to line numbers in generated Java code.

  • XQuery is statically typed, which helps in catching errors early.

Pages: 1, 2, 3, 4, 5, 6

Next Pagearrow