ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

XML Publishing with Cocoon 2, Part 1

by David Cummings and Collin VanDyck


One of the most important decisions to make when deciding to build an application is "Upon what foundation should I build?" This is a two-fold question, depending not only upon the language in which the application will be developed, but also the framework, if any, that will provide fundamental services and infrastructure to your system.

Cocoon is essentially an XML-publishing framework. That means that it facilitates the generation, transformation, and serialization (delivery) of XML content. Super! Also, Cocoon is written in Java, giving it many benefits from the cross-platform and code manageability perspectives. The information presented here was gained from our development of ContentXML Content Management Suite, an enterprise content-management system built on top of Cocoon 2.

Let's take a percursory look at Cocoon before we get into the thick of it. Because Cocoon is built around a web context, the flow of control is centered around the familiar request-response web paradigm. To illustrate how Cocoon is involved in this model, a simple example will suffice.

The user initiates a request from a web browser for a particular HTML document, say, hannonhill.com:8080/app/get/pressrelease/384792. Note that this request is most likely generated from a link on your site. The port 8080 is where Cocoon listens by default on our Tomcat/JBoss installation. Cocoon is installed as a web application archive (WAR), mounted at /app on hannonhill.com. Thus, Cocoon receives get/pressreleases/384792 as the URI of the request. This URI is passed off to the Cocoon sitemap, which performs some magic and then returns the result of the processing to the user as HTML.

Related Reading

Java & XML Data Binding
By Brett McLaughlin

If you didn't get all of that, no worries. It will all become clear in due time.

The Cocoon Sitemap

The Cocoon Sitemap is the entry point for all Cocoon-related requests. It is an XML text file (that is eventually compiled into a Java class automagically) and is typically named sitemap.xmap. It is logically divided into two sections: components and pipelines.


Speaking of frameworks, Cocoon itself is built upon the Avalon Framework, which, to quote: defines relationships between commonly used application components, best-of-practice pattern enforcements, and several lightweight convenience implementations of the generic components.

Avalon makes it very easy to plug in custom components to other applications built upon the Avalon framework. This is exactly what we can do in the first part of the Cocoon sitemap, the components section. Being able to write our own components and plug them in is extremely convenient, as we are able to customize the framework to do our own bidding. Before I start wringing my hands, let's table the components section and move on to the pipelines.

You can think of a Cocoon pipeline as a series of ordered buckets into which certain requests may fall into and be processed. A pipeline consists of one or more matchers that basically return true or false, with the true state implying processing by the components that are encapsulated by the matcher. To illustrate, a request is made. This request has a URI. Cocoon loads the sitemap, looks at the pipeline, and starts comparing the URI against each matcher (bucket) from top to bottom. When it reaches a matcher that evaluates to true, flow of execution transfers to the code inside of that matcher XML element.

You have probably guessed by now that the matchers themselves are components that you can plug in and swap out, if you want to match against something other than the request URI. For instance, we have developed matchers that evaluate a user's membership within a particular group, or examine request parameters to test for certain conditions.

The question arises: "How do you implement your own matcher?" Simple. Create a class that implements org.apache.cocoon.matching.Matcher, which mandates that the following method be implemented:

public Map match(String pattern, Map objectModel, Parameters parameters);

Cocoon supplies, at run time, environment-related data (through the objectModel object) and special parameters that you can pass into your custom matcher through the sitemap (through the parameters object, which is basically a hashmap of key/value pairs). Once this class has been deployed in your WAR, invoke it in the sitemap like any other matcher. Here is an example sitemap that employs a custom matcher:

<?xml  version="1.0" encoding="ISO-8859-1"?>
<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
    <map:matchers default="wildcard">
      <map:matcher name="wildcard" 
      <map:matcher name="premium-customer"
      <map:matcher name="request" 
               <map:match pattern="get/pressrelease/*">
                       <map:match pattern="goldCustomer" type="premium-customer">
                               <map:generate src="docs/pressreleases/{../1}.xml"/>
                               <map:transform src="xslt/premiumPressrelease2html.xsl"/>
                               <map:serialize type="html"/>
                       <!-- executed if not a gold customer -->
                       <map:generate src="docs/pressreleases/{1}.xml"/>
                       <map:transform src="xslt/pressrelease2html.xsl"/>
                       <map:serialize type="html"/>

As you can see, we have listed a number of different matchers in the components section of our sitemap. Also, note that this is quite an incomplete sitemap. I have only shown those items that are relevant to the discussion at hand. For each matcher that you list, you must supply a name and a source class file. The name is how you will reference that matcher in the pipeline section of the sitemap, and the src attribute is the fully qualified name of the class that implements that component; in this case, that matcher. So you may say from our example above that the custom matcher implemented by the class com.hannonhill.cocoon.matching.PremiumCustomerMatcher is referenced elsewhere in the sitemap by the name premium-customer.

Before we get into our own custom matcher, note that it is wrapped in a higher-level matcher that does not specifically list a type. This is assumed then to be the default matcher that, from the components section of our sitemap, we assume to be the "wildcard" matcher. This is a matcher supplied in the Cocoon distribution that matches a request URI against a certain string, including wildcards. There are two wildcards, * and **. The former matches any string, excluding the / character, while the latter matches any string whatsoever, including the / character.

A matcher indicates a successful match by returning a Map (possibly empty, but definitely non-null). This Map may contain parameters that are available later on in the sitemap through variable interpolation. In our example, the request URI get/pressrelease/384792 would match our first matcher and the wildcard would match the string 384792, returned to the sitemap through the Map. This string could be referenced later in the sitemap through the {1} string. Thus, <map:generate src="docs/pressreleases/{1}.xml"/> would evaluate to <map:generate src="docs/pressreleases/384792.xml"/>.

You might wonder what {../1} means. Since the Maps returned by the matchers are nested, you can traverse them in much the same way as you would traverse a directory. Thus, the {../1} variable interpolation is not looking in the Map returned by the premium-customer matcher, but instead one level up, in the wildcard matcher! Your custom matchers will usually return a sitemap parameter with a meaningful name (for readability's sake). This particular Cocoon matcher uses numbers to indicate which wildcard match to dereference. Thus, a match pattern of get/*/* would match our URI with {1} containing pressreleases and {2} containing 384792.

Our custom matcher would implement the following method:

public Map match(String pattern, Map objectModel, Parameters parameters) {
        if ("goldCustomer".equals(pattern)) {
               if (customerIsGold()) {
                       return true;
               } else {
                       return false;
        } else if ("silverCustomer".equals(pattern)) {        }

From this, you can see that Cocoon is responsible for instantiating our custom matcher and executing the match() method to determine if the code inside of that <map:match> statement is executed. Otherwise, it skips the code inside and go to the code directly below it.

How exactly does Cocoon generate information? Glad you asked.

Pages: 1, 2

Next Pagearrow