XML Publishing with Cocoon 2, Part 2
Pages: 1, 2
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:
<source-handler logger="core.source-handler">
<!-- file protocol : this is a WriteableSource -->
<protocol
class="org.apache.cocoon.components.source.FileSourceFactory"
name="file"/>
<!-- contentxml pseudo protocol -->
<protocol
class="com.hannonhill.cocoon.components.source.ContentXMLSourceFactory"
name="contentxml"/>
<!-- xmldb pseudo protocol -->
<protocol class="org.apache.cocoon.components.source.XMLDBSourceFactory"
name="xmldb">
<!-- Xindice driver -->
<driver class="org.apache.xindice.client.xmldb.DatabaseImpl" type="xindice"/>
<!-- Add here other XML:DB compliant databases drivers -->
</protocol>
</source-handler>
You can see that we have added our own class,
ContentXMLSourceFactory, as the handler for the
contentxml pseudo-protocol. This allows us to use the following in
the sitemap:
<map:match pattern="getEntityXML/*">
<map:generate src="contentxml:entity-xml://{1}"/>
<map:serialize type="xml"/>
</map:match>
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.
Note that we could have defined our protocol as:
contentxml:entity-xml:{1}:
if we wished.
Delegating from the SourceFactory
Your source factory must implement
org.apache.cocoon.components.source.SourceFactory, which mandates
this particular method:
org.apache.cocoon.environment Source
getSource(Environment environment, java.lang.String location);
The string that we formed our generate call with is passed in as the
location parameter. Essentially, you would then check for each
possible message in the SourceFactory:
String action = parseAction(location); // defined elsewhere.
if action.equals("entity-xml") {
return new EntityXMLSource(...);
} else if (action.equals("search-results")) {
return new SearchResultsSource(...);
}
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:
public void toSAX(ContentHandler handler)
throws SAXException, ProcessingException;
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
Sourceobject gathers information from the database, and constructs a JDOM tree. - We create SAX events from our JDOM tree and send them to the
ContentHandler.
In Java, this looks like:
public void toSAX(ContentHandler handler)
throws SAXException, ProcessingException {
try {
// create a jdom outputter
SAXOutputter outputter = new SAXOutputter(handler);
// defined elsewhere
Document stylesheetDocument = getObjectListAsJDOM();
// send our document as SAX events
outputter.output(stylesheetDocument);
} catch (JDOMException e) {
throw new ProcessingException("Error outputting XML: " + e);
} catch (RemoteException e) {
throw new ProcessingException("Remote error outputting XML: " + e);
}
}
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.
Collin VanDyck is the lead developer of ContentXML and an integral part of the Hannon Hill team.
David Cummings is the CEO of Hannon Hill Corporation which focuses on content management software solutions.
Return to ONJava.com.