XSP (eXtensible Server Pages) is Cocoon's technology for building web applications based on dynamic XML content.
Beyond static content (i. e., hand-written documents produced by web authors), web applications demand dynamic content generation capabilities, where XML documents or fragments are programmatically produced at request time.
In this context, content is the result of computations based on request parameters and, often, on access to external data sources such as databases or remote server processes.
This distinction in content origin extends the "traditional" regions of web publishing (content and presentation) to also encompass that of logic.
Dynamic web content generation has traditionally been addressed by embedding procedural code into otherwise static markup.
This approach is fully supported by XSP. Consider the following example:
Upon XSP processing, this XML fragment will yield:
before noon and:
afterwards.
While the above may appear simple (to a Java developer, that is!), XSP has been conceived to allow web authors to generate dynamic content without forcing them to learn a programming language.
Thus, XSP allows us to rephrase our example as:
where <util:time-of-day/>
is a
library tag encapsulating dynamic content
in a simple, transparent way.
This feature promotes an ideal division of labor where:
Of course, for those of us subject to "real world" constraints, XSP also supports the time-honored approach of first using embedded logic and then incrementally evolving the resulting pages into a well-structured web application.
These concepts are illustrated in the XSP Samples included in the distribution.
An XSP page is a Cocoon XML document containing tag-based directives that specify how to generate dynamic content at request time.
Upon Cocoon processing, these directives are replaced by generated content so that the resulting, augmented XML document can be subject to further processing (typically an XSLT transformation)
XSP pages are transformed into Cocoon producers, typically as Java classes, though any scripting language for which a Java-based processor exists could also be used.
Directives can be either XSP built-in processing tags or user-defined library tags. XSP built-in tags are used to embed procedural logic, substitute expressions and dynamically build XML nodes. User-defined library tags act as templates that dictate how program code is generated from information encoded in each dynamic tag.
In the following XSP page, the Java language is used to generate some dynamic content:
Upon Cocoon processing, this page should yield something like:
Let's dissect this example:
Two processing instructions are used in our example to control how Cocoon processes its XML content:
<?cocoon-process type="xsp"?>
<?cocoon-process type="xslt"?>
<?xml-stylesheet href="sample.xsl" type="text/xsl"?>
Note that it is not mandatory for an XSP page to be further processed by XSLT. Any (or none) other Cocoon XML processing step(s) may be subsequently applied depending on application requeriments.
All XSP directives belong to the <xsp:>
namespace.
Also, XSP pages are required to have an
<xsp:page>
root element:
The <xsp:page>
root element can specify
the programming language used in the XSP page by means of
the language
attribute. If this attribute is
omitted, java will be used.
The root element must also specify the XSP namespace URL
by means of the xmlns:
attribute. As will be
explained later, this attribute is also used to introduce
user-supplied tag libraries.
Finaly, the XSP root element must contain only one
non-XSP nested element. Such element (<page>
in the above example) will become the root element of the
generated document.
In our example, the "user" root element contains:
Here, we use the two essential XSP logic tags:
<xsp:logic>
. This tag encloses
developer-supplied program logic. Such logic will be
transcribed verbatim into the generated Cocoon
producer.
<xsp:expr>
. This tag evaluates
a program expression and substitutes its value as
a Text
DOM node in the resulting
document.
In the fragment:
a new variable (now
) is declared that can
be subsequently referenced anywhere in the remaining
page content.
Note that, in this particular example, it's not necessary
to fully specify the Java type (java.util.Date
)
because XSP automatically generates import
statements for the most commonly used Java libraries.
In the fragment:
the variable now
is referenced so that its
value is inlined in the resulting XML document as a
Text
node.
Note also that it's not necessary to explicitly cast
now
to String
.
<xsp:expr>
takes care
of converting all Java types so that values are properly
converted depending on context.
In general, <xsp:expr>
values are inlined
according to the following rules:
int
, long
,
etc) are converted to their String
representation
and wrapped as Text
String
s are directly wrapped as
Text
DocumentFragments
and each element is recursively applied these same
transformation rules.
String
(by means of their toString
method)
and subsquently wrapped as Text
.
Note that some types may not provide a suitable
string representation, so they may require a
more elaborate expression
A nice XSP feature (not present in other server pages
technologies) is that the <xsp:logic>
element allows for the arbitrary nesting of other markup
without the need to "prematurely" close it. For example:
</table>
If such nesting was not allowed, the purely programmatic alternative would be considerably more complex:
Note, however, that it is not allowed to nest
<xsp:expr>
tags directly inside
<xsp:logic>
. For an expression
to be inlined inside an <xsp:logic>
element, it must be escaped by surrounding it with an
<xsp:content>
tag. Example:
The observant reader may have noticed a rather
unpleasant feature in the above code: the
less-than sign (<
)
must be represented as <
!
This is an undesirable (but unavoidable) consequence
of the <
and &
characters being special to XML parsers.
A workaround is to escape code chunks contaning the
less-than (<
) and
ampersand (&
) characters as
CDATA sections:
</table>
Caution must be exercised, though, to avoid enclosing
static markup inside the <![CDATA]]>
section, as this will result in syntax errors upon
compiling the generated Cocoon producer!
In the following example, a developer-supplied tag library is used instead of embedding procedural code:
Here, the web author is shielded from programming complexities at the expense of additional developer effort in devising and implementing a proper dynamic tagset.
As mentioned early, this is the "ideal" XSP scenario: dynamic content generation requirements are identified before hand and represented in reusable tag libraries. Web authors can then focus on their their "true" purpose in life: producing content.
XSP uses XSLT stylesheets for source code generation. Thus, each dynamic tag in a library is supported by an XSLT template containing the program logic to be generated. Upon execution, generated logic will yield the dynamic content encoded by its underlying dynamic tag.
Without yet delving into the finer details of the XSP object model, let's see how the example tag library stylesheet looks like:
Let's focus our attention on 4 key features of this library:
Each XSP library defines a separate XML namespace:
Thus, all dynamic tags should belong to the same namespace associated with their defining library
While this discipline is not enforced by XSP, developers are strongly encouraged to follow this pattern because it prevents ambiguities and promotes document readability.
The <xsp:structure>
section provides
the context for program-level declarations such
as the Java import
directive.
The <xsp:structure>
tag may contain
zero or more <xsp:include>
directives,
each specifying an external program module to be made
available to the generated XSP program.
Note that the exact semantics of this elments is
language-dependent. For Java, it corresponds to
an import
statement. For Fesi
Javascript, for example, it may be interpreted as
a load()
function call.
In our example, we're importing the
java.util.Date
and
java.text.SimpleDateFormat
class definitions.
In all honesty, though, including
java.util.Date
is redundant because
XSP always imports java.util.*
. It's
included here only to emphasize that the
example library depends on it.
java.text.SimpleDateFormat
, on the other
hand, must be included, because it's not among
XSP's "preferred" Java packages.
In general, XSP class-level declarations (such as instance
variables or methods) are defined by means of
<xsp:logic>
blocks
placed outside the user root element.
XSP libraries exploit this feature to declare class-level variables and methods used by code generated in reponse to the use of dynamic tags in XSP pages.
In our example, the following method is used to generate
a String
representation of the current system
time:
Dynamic tag templates may then refer to this method.
Finally, each dynamic tag must provide an associated XSLT template that dictates what source code must be generated whenever the tag is encountered in an XSP page.
Note that the source code to be generated must be
enclosed in either an <xsp:expr>
or an <xsp:logic>
element. This
is so because these XSP tags will be later evaluated
by the XSP built-in library (itself an XSLT stylesheet!)
In this example, each reference to a
<example:time-of-day>
tag
will be replaced by a corresponding
<xsp:expr>
"call". Thus,
if an XSP page using the example
library contains:
XSP will expand this reference to:
during library processing. Upon source program generation this directive will be finally expanded to:
where the xspExpr()
built-in method is
overloaded to wrap all possible Java types as a
Text
Node.
Finicky developers may prefer to map dynamic tags to bean method calls, as opposed to inlining "raw" Java code.
While some may consider this a matter of taste, using bean properties and methods is certainly advisable because it isolates library code from implementation details. This would allow, for instance, for a complex Enterprise Java Bean to be modified without impacting existing XSP pages.
<xsp:page>
<xsp:structure>
<xsp:include>
<xsp:include>
<xsp:logic>
XSPPage
producer. Other
XSP or user markup may be nested inside
this tag
<xsp:content>
<xsp:logic>
block
so that no nested additional
<xsp:logic>
sections are required
<xsp:expr>
Text
node,
except when used directly inside another
<xsp:>
element,
where it is substituted as an
expression, not a node. If you
want to substitute an
<xsp:expr>
tag
as a node inside another XSP
tag, you must enclose it in an
<xsp:content>
element.
<xsp:element>
<xsp:attribute>
<xsp:element>
).
This tag is typically used in conjunction
with <xsp:expr>
,
where the substituted expression is
always cast to String
<xsp:pi>
<xsp:comment>
The <xsp:page>
root element
has an optional language
attribute
that defaults to java. Other scripting
languages (both interpreted and compiled) will
be supported in the near future.
The <xsp:page>
root element
requires one or more xmlns:
attributes
specifying tag libraries. These attributes specify
each tag library's DTD location as a URL.
At least the built-in XSP (currently,
xmlns:xsp="http://www.apache.org/1999/XSP/Core"
)
library must be specified.
The order in which xmlns:
attributes appear
dictates library processing order. The XSP built-in
library, though, is always applied last, regardless of its
position in the xmlns:
attribute list.
The <xsp:page>
element can only
have one user element. This element
becomes the root of the generated XML document.
Only the following node types are valid as direct
descendants of the <xsp:page>
root element:
<xsp:structure>
elements.
<xsp:logic>
elements.
<xsp:logic>
blocks that are direct descendants of the
<xsp:page>
root element
(i. e., placed outside the
user root element) are generated as
class-level logic. This is used for declaring
class fields and methods.
Top-level processing instructions (other than
<?cocoon-process type="xsp"?>
)
are preserved in the generated XML document.
This is convenient, for example, for subsequent
XSLT processing.
Some Java packages are always imported by
default. As such, they do not require
explicit <xsp:include>
directives. These packages are:
java.io.*;
java.util.*;
org.w3c.dom.*;
org.xml.sax.*;
javax.servlet.*;
javax.servlet.http.*;
org.apache.cocoon.parser.*;
org.apache.cocoon.producer.*;
org.apache.cocoon.framework.*;
org.apache.cocoon.processor.xsp.*;
<xsp:content>
can be used inside
<xsp:logic>
blocks to avoid the recursive nesting of
additional <xsp:logic>
elements. Thus, the following XSP fragment
(which contains nested
<xsp:logic>
blocks):
can be rewritten as:
Note that this does not work in cases where a
non-empty element would be "truncated" by an
interspersed <xsp:logic>
block. Thus, the following example cannot be
reduced by means of <xsp:content>
because the second <td>
would be
"cut short" by any intervening
<xsp:content>
.
The <xsp:element>
tag
(which requires a name
attribute)
is used in those cases where the element
name is known at compile time, but its
attribute values are not. For example:
Only XML text and <xsp:expr>
are
valid inside an <xsp:attribute>
element.
Note that whitespace is significant inside
<xsp:attribute>
so indenting <xsp:attribute>
content may result in spurious space being
generated. Unless you really need
to generate spaces as part of the attribute
value, resist the temptation to write something
like:
Finally, if an element name is not known at compile time, you must programatically create it:
This may be alleviated in future XSP versions by introducing an idiom similar to the XSLT way of substituting dynamic expressions inside attributes values:
The <xsp:pi>
element
requires a target
attribute
specifying the processing instruction name.
<xsp:expr>
is not yet
supported to provide a value for a processing
instruction's data. Only textual, constant
values are allowed:
In the future, <xsp:pi>
may be
renamed to <xsp:processing-instruction>
to achieve compatibility with XSLT.
Note that dynamically created comments
(<xsp:comment>
)
may be removed by subsequent XSLT processing.
A processor is a Cocoon Java type that takes a DOM tree as input and produces another (possibly modified) DOM tree as output. This concept is similar to that of a Unix "filter" in a command pipeline.
The XSP engine is implemented as a Cocooon processor that accepts an XSP page as input. The first time a given XSP page is processed, it is translated into an equivalent source program which is then compiled, loaded and executed. Subsequent requests for the same XSP page result in the execution of the generated program. As you may expect, the output DOM tree returned by the XSP engine processor is actually built by the generated program.
XSP pages are compiled into Cocoon producers. A producer is a Cocoon Java type normally used to "feed" the initial XML content to the Cocoon processing pipeline.
Thus, for example, when Cocoon serves a static, "regular"
XML document, file contents are actually delivered by
Cocoon's built-in FileProducer
.
Whereas other related server pages technologies (such as JSP) generate servlets, XSP generates producers instead. This is so because, among other reasons, the servlet model does not yet provide a mechanism for portably and efficiently post-processing XML content.
XSP defines an abstract producer (XSPPage
)
as the base class for generated programs.
This class exposes a simple object model that can be easily used by XSP developers to programmatically control how XML content is generated.
The following objects are accesible inside an XSP page:
request
.
A Cocoon-supplied wrapper to the standard
HttpServletRequest
object.
This wrapper provides all the functionality
defined for its JSDK interface counterpart.
This object is typically used to retrieve
Http form parameters as well as to get
header and cookie information.
response
.
A Cocoon-supplied wrapper to the standard
HttpServletResponse
object.
This wrapper provides most of the
functionality defined for its JSDK
interface counterpart,
except for access to the
ServletOutputStream
object
and its associated writer.
This restriction
is necessary to ensure consistent Cocoon
servlet output and does not impose any
limitation to XML processing. Other suitable,
non-output operations (such as setting headers,
cookies or content types) are allowed
session
.
The standard HttpSession
servlet
object. This object is typically used to
store data associated with a user HTTP session
servletContext
.
The standard ServletContext
object. This object is typically used to
store application-level data by means
of its setAttribute
and
getAttribute
methods. Other
uses include determining the real path
of a URL as dictated by the underlying web
server virtual directory structure. Note
that the application-level data sharing
capabilities offered by this object are
available only for JSDK version 2.2 and
higher. To circumvent this for JSDK prior
to 2.2, use the xspGlobal
object (explained below)
document
.
An XSP-supplied org.w3c.Document
object. Initially empty, this object is populated
by the generated XSPPage
producer
and is typically used as a node factory
(document.createElement()
,
document.createProcessingInstruction()
,
etc)
xspGlobal
.
An XSP-supplied global dictionary offering
the same setAttribute
and
getAttribute
services supported
by the standard ServletContext
object. This surrogate exists only to provide
application-level data sharing for older
servlet engines (prior to 2.2). Note that,
while providing a means for application-level
data sharing for older servlet engines,
such global data cannot be shared with non-Cocoon
servlets or JSP pages. For this purpose, use
the standard servletContext
object.
xspGlobal
may be deprecated in future
XSP versions
xspNodeStack
.
A java.util.Stack
used to control
element nesting in the XSP page Document
object. Extreme caution must be exercised in
using this object as all DOM manipulations
take effect on its top element. Note:
java.util.Stack
was introduced
in Java2. It should be replaced by an
equivalent JDK1.1 implementation!
xspCurrentNode
.
An org.w3c.Node
object corresponding
to the node being currently populated
xspParentNode
.
An org.w3c.Node
object corresponding
to the parent of the node being currently
populated. This object is normally the top element
of the xspNodeStack
xspParser
.
A Cocoon-supplied DOM parser which may be used
to create new documents and parse external
XML documents
In addition to the above objects, the XSPPage
contains an xspExpr()
method than can be
used to wrap any Java value as an org.w3c.Text
object. Example:
In addition to the above infrastructure objects and methods, XSP provides a utility class offering a number of DOM, HTTP and file manipulation services implemented as public static methods:
Node cloneNode(Node node, Document factory)
.node
argument using the factory
document as the
creator for the copied nodes. This is typically used to
embed external XML documents into the XSP page
document
object. This is required because
DOM Level 1 parsers do not allow for a node to be
appended as child to another node if the two belong
to different document instances
String toMarkup(Node node)
.node
argument. This is typically
used to embed a textual, non-DOM representation of
external XML files as well as for debugging purposes
String encodeMarkup(String string)
.string
argument
replacing occurrences of markup delimiters as
follows:<
is replaced by <
>
is replaced by >
&
is replaced by &
String formEncode(String text)
.text
argument
to x-www-form-urlencode format, as required
by HTTP form query strings. This method is actually
a wrapper for java.net.URLEncoder.encode
String formDecode(String text)
.text
argument
from x-www-form-urlencode format to a
String
representation. This method is
actually a wrapper for
java.net.URLDecoder.decode
(introduced
in Java2) and exists solely to provide this
functionality for JDK1.1
String pathComponent(String filename)
.filename
argument as a file name and removes the last
component to yield only the directory path
information.
This conversion is performed in an operating
system-independent manner
String fileComponent(String filename)
.filename
argument as a file name and removes the leading
path component to yield only the file name
portion, including the file name extension,
if any.
This conversion is performed in an operating
system-independent manner
String baseName(String filename)
.filename
argument as a file name and removes the leading
path component to yield only the base file name
excluding the last dot file
extension, if any.
This conversion is performed in an operating
system-independent manner
String baseName(String filename, String suffix)
.filename
argument as a file name and removes the leading
path component to yield only the base file name
excluding the last occurerence of the
extension given by its
suffix
argument.
This conversion is performed in an operating
system-independent manner
String normalizedBaseName(String filename)
.filename
argument as a file name and removes the leading
path component to yield only the base file name
excluding the last dot file
extension, if any. The resulting filename is
then scanned for non-alphanumeric characters
which are replaced by underscore
(_
). An underscore is also
preprended to each directory component.
This is used to map file names to valid Java
identifiers.
This conversion is performed in an operating
system-independent manner
String relativeFilename(String filename, HttpServletRequest request, ServletContext context)
.filename
relative to the request
URI. This is typically used to open operating system files
given a name relative to the request's virtual path
String relativeFilename(String filename, HttpServletRequest request)
.filename
relative to the request
URI. This is typically used to open operating system files
given a name relative to the request's virtual path. This
variant depends on the deprecated
HttpServletRequest.getRealPath
method and
exists only for compatibility with older JSDK's in which
the ServletContext
object did not provide
a getRealPath()
method
String[] split(String line)
.line
string
argument to a String
array using
whitespace (blanks, tabs, carriage returns and
newlines) as field separators
String[] split(String line, String delimiter)
.line
string
argument to a String
array using
the characters in its delimiter
argument as field separators
boolean isAlphaNumeric(char c)
.c
argument to
assert whether it is an underscore, a lowercase or
uppercase letter or a digit
XSP has been designed to support other scripting languages, in addition to Java. In principle, any programming language for which a Java-based interpreter exists could be used to script XSP pages.
In general, languages supporting compilation to bytecodes, such as Java itself, Netscape's Rhino Javascript or IBM's NetRexx, perform significantly better than interpreted languages like FESI Javascript or WebL.
Interpreted languages, however, are expected to play an important role in XSP scripting. This is so because (in addition to their typical ease of use) more and more field-specialized languages are being developed for the JVM. Such specialized scripting languages usually offer more expressive power and conciseness than strictly object-oriented languages.
For a fairly complete list of such languages see Programming Languages for the Java Virtual Machine.
Until recently, Java lacked a well-defined scripting architecture that allowed applications to incorporate scripting easily. IBM's Bean Scripting Framework (BSF) is an Alphaworks project providing such architecture.
BSF supports a growing number of scripting languages including Rhino, NetRexx, Jacl and JPython. On the Windows platform, BSF also supports JScript, VBScript, and PerlScript.
Currently, XSP only supports Java as a scripting language, though its design allows for an easy integration of other languages.
In the near future, BSF will be integrated to XSP's own language abstraction mechanism so that most (if not all) of the above mentioned languages may become available for XSP scripting.
XSP tag libraries are language-dependent. Thus, the addition of a new language involves (at the very least) porting the XSP built-in libraries. An XSP library is composed of a code-generation XSLT sylesheet and an optional library preprocessor class.
A library's code-generation stylesheet is simply a "regular" XSLT stylesheet responsible for translating user-defined dynamic tags into equivalent XSP tags for a given scripting language (sometimes called logicsheet). While a preprocessor is an optional library Java class used to augment an XSP page DOM tree prior to applying the library code-generation stylesheet.
Library preprocessors may be language-dependent. Early experience suggests, though, that the same preprocessor class will be typically shared by different languages for the same dynamic tagset.
XSLT extension functions and extension elements may replace library preprocessors in the future. In this case, all code-generation logic would reside in the library's stylesheet.
At this early development stage, we still don't know how the implementation will evolve and we look forward for public feedback before going any further along with the implementation details.
XSP is currently in an early development stage.
As such, some of the functionality described in this document may be buggy or incomplete.
So far, XSP has been tested only with:
XSP requires the following properties to be added to your cocoon.properties configuration file:
The XSP repository is a physical (not virtual!) filesystem directory that will contain the generated Java source and class files. This directory must exist in advance and must be readable and writable by the operating system user running the servlet engine and/or web server processes.
Thus, in the above cocoon.properties configuration file, PATH_TO_XSP_CLASS_STORE must be replaced by the correct location of this directory.
The file xsp-libraries.xml
is an XML configuration file containing
a list of XSP library descriptors.
This file must exist and must be
readable by the operating system user
running the servlet engine
and/or web server processes.
The xsp-libraries.xml
included in the initial XSP distribution
contains:
Note that the stylesheet file
util-java.xsl
will be located relative to
the location of the
xsp-libraries.xml
file.
If you want to place a library stylesheet file in a different location, you must specify its fully qualified pathname. Example:
Also, if your library requires a
preprocessor class, it must be
accessible to the servlet engine
through its CLASSPATH
system property.
This initial implementation of XSP has been developed by Exolab and is being donated to the Apache project as part of Exoffice's commitment to the open source movement.
Special thanks must be given to Ismael Ghalimi for sponsoring and supporting XSP development.
Last, but not least, cheers to Stefano Mazzocchi, founder and leader of the Cocoon project and original author of the XSP specification. Thanks!!!