XML Publishing with Cocoon 2, Part 2

In our previous
article, we covered the basics of sitemap navigation in Cocoon (the Cocoon
XML publishing framework process), creating custom XML generators through the
use of XSP, and using the XSP to homogenize the look and feel of sitewide
content, specifically forms.

Picking up from where we left off, we had just generated a login form that
submitted a username and password. Typically, all of our forms submit data
back into Cocoon and to a URI that matches code in the sitemap that invokes a
Cocoon Action. Actions are simply Cocoon components that implement
org.apache.cocoon.acting.Action. Specifically,

must be implemented. From the parameter list, you can see that the
environment objectModel is passed in, from which you can derive
the request object and also any sitemap parameters that are passed in.
Information is passed into the action through the request and the sitemap
parameters. Sitemap parameters are denoted through XML elements in the
sitemap. For example:

Sitemap parameters must come directly after the use of the component. Much like
matchers, you can see that the Action returns a Map.
This object is non-null if the Action was successful, and
null if the Action failed in any way. In the same way as the
matcher, the sitemap nesting reflects this if-then-else logic.

The other way we send information to Actions is through the request object.
When a form is submitted, the form inputs are encoded as request parameters.
You can derive the request object through the objectModel:

We use a combination of both sitemap parameters and request parameters in
our Actions. Typically, we will use sitemap parameters to denote functionality,
and the request parameters will contain the actual data with which to work.

Message Handling with Actions

Every web application should give some sort of visual feedback to the user
once something of significance has happened; in our case, the user has logged
in. Let's say our user should then receive a message saying so. We designed
our application such that all important things were accomplished through our
use of Actions (which delegated some tasks to our J2EE back end as well).
Because of this, we decided that it made a lot of sense to enable our Actions
to pass messages back up to our GUI front end.

In order to pass messages, we insert objects into the Cocoon Request
object. You saw earlier how every Action has access to the
Request object through the object model. The great thing about the Request
object is that you may place arbitrary numbers and types of objects into it
as key-value pair attributes of that Request:

request.setAttribute("message","You have just logged in!");

However, just passing a String as a message doesn't really seem
flexible enough for our needs. We need to be able to send multiple messages out
of a Request. For example, what if the user did not fill out three of the form
fields correctly?. To that effect, we created a wrapper class around a
Vector, called Messages, which supported
addMessage() and addError(), as well as a way to
iterate through those messages and errors.

The abstract classes handle the instantiation of the Messages
wrapper and other overhead. Any class that inherits from these could simply
call error() to its heart's content, committing those errors into
the request object with commitErrors() before returning
null. Because the Request object is visible throughout the life
of the request-response lifecycle, the GUI will be able to retrieve the
messages from the Request object and display them accordingly.

Source Factories in Cocoon

We saw earlier how to generate XML SAX events from a file containing XML,
and then how to dynamically generate those SAX events using XSP and embedded
Java code. However, there are times when we need to generate more complex XML. An
example of this would be the search results page: search criteria are
specified, and the system must build an XML tree of the search results. This
would be difficult with XSP, to say the least.

Cocoon Configuration

Before we get into how to do this, let's quickly visit the world of Cocoon
configuration. You'll find in the Cocoon distribution a file called
cocoon.xconf in WEB-INF/ that defines the
configuration parameters for Cocoon.

In this file, you may define new protocols for what is called the source
handler. Don't worry too much about the source handler right now. Here's the
excerpt for the source handler in cocoon.xconf:

This is a simple example for illustration purposes. We have many different
types of content, and each type of content may be represented as XML. Sending
the entity-xml message to our contentxml protocol
should generate the XML for that entity. You might ask, which entity? See the
variable interpolation after the ://? That's where we pass in our
extra information, if any, to our source generators.

Creating Source Objects from the Factory

True to the definition of a factory, our extension of
SourceFactory is simply responsible for parsing the input message,
and returning a Source object. This is a Cocoon object that
implements the Source interface, inheriting the
toSAX() method:

This is where the heart of the functionality for the Source
happens. Most all of the other methods are responsible for setting up the
object, and once all of that is finished, the object is ready for Cocoon to
invoke the toSAX() method. Cocoon supplies the
ContentHandler to this method. You can think of that as the
object that receives the SAX events that this Source object will
generate. This object could be a transformer, or possibly a serializer. That's
out of the control of this component, so we'll not address it. All this
component knows is that it must generate some XML, parse it, and send the SAX
events from that XML to the ContentHandler supplied in the
toSAX() method. Whew!

We've found JDOM to be very useful in our XML generation. JDOM is
essentially a way of building an XML document outside of DOM and SAX. It
provides ways of integrating with both DOM and SAX, and its API is extremely
simple to use. Our algorithm then looks like this:

toSAX() is called by Cocoon.

Our Source object gathers information from the database, and constructs a
JDOM tree.

We create SAX events from our JDOM tree and send them to the
ContentHandler.

Neat! With the simplicity of our Sources, the difficult part here is to
build the JDOM document. That, unfortunately, is outside of the scope of this
tutorial, but you can find documentation, including Javadocs, at jdom.org.

Summary

Cocoon provides a true XML publishing framework that is very powerful and
pluggable with custom components. Learning Cocoon takes a decent amount of time,
but the rewards are enormous. I hope this quick technical overview of Cocoon,
gained through experiences with our ContentXML product, can help you with your
next XML publishing application.