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 thefor-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 testingempty($i)and calling the Qexoerror-responsefunction. 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
picturefunction 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 thepicturefunction 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.