Transform data into Web applications with Cocoon

Use Java to implement logic in Cocoon

You might have heard some of the recent buzz about Cocoon from the Apache Software Foundation. Now in its third year of development, Cocoon has gradually matured from a simple XSL (Extensible Stylesheet Language) transformation servlet into a full-blown Web application framework. Developed in Java, Cocoon typically runs as a servlet inside a servlet container like Apache Tomcat, though it can run in standalone mode as well.

Now don't be put off by the mention of XSL, because Cocoon has a great deal to interest the Java developer. In this article, we look at two ways of using Java to build business logic into your Cocoon-based Web applications. But first, let's start with a quick overview of Cocoon.

Cocoon is officially defined as an XML publishing engine, and while technically correct, the description does not do the product justice. The best way to understand Cocoon is to view it as a framework for generating, transforming, processing, and outputting data. Think of Cocoon as a machine that receives data from a wide variety of sources, applies various processing to it, and spits data out in the desired format. Figure 1 helps illustrate this view.

Figure 1. Cocoon big picture

We could also define Cocoon as a data flow machine. That is, when you use Cocoon, you define the data paths or flows that produce the pages that make up your Web application. Even a simple hello-world.html page has a data flow defined to serve the page.

I could spend this entire article discussing Cocoon's architecture, but it boils down to a few basic principles:

  • Cocoon handles all data internally as SAX (Simple API for XML) events; any non-XML input data converts to an XML representation.
  • Components called generators—because they generate SAX events—handle data input.
  • Components called serializers handle data output—they write the data out to the client (that is, browser, file, and so on).
  • The developer combines generators, serializers, and other components to form processing flows called pipelines. All pipelines are defined in a file called the sitemap.
  • URI (Uniform Resource Identifier) patterns identify pipelines, but URIs are completely decoupled from physical resources.

This last point warrants some emphasis. In a traditional Web server, a URI generally maps to a physical resource. So, the URI http://localhost/index.html on an Apache server maps to an HTML file I create on my computer called index.html. In Cocoon, there is absolutely, I repeat, absolutely no inherent correlation between URIs and physical resources. While nothing prevents you from making a correlation, Cocoon does not require one. You are free to design the URI patterns for your application in a way that helps your users better navigate your site. On the back end, you can organize your file resources to facilitate administration and maintenance.

To better understand Cocoon's processing model, let's look at a sample pipeline. This rudimentary example defines a page called index.html. This pipeline lives in the sitemap, which is an XML file typically called sitemap.xmap:

    <map:match pattern="index.html">
      <map:generate type="file" src="content/mainfile.xml"/>
      <map:transform type="xslt" src="content/stylesheets/mainstyle.xsl"/>
      <map:serialize type="html"/>
    </map:match>

This pipeline has three steps. First, a generator component, the FileGenerator, reads data from an XML file called content/mainfile.xml. (The FileGenerator's actual definition is made earlier in the sitemap, at which time, the component is assigned a type attribute called file. All pipeline components in Cocoon are referenced by their type attributes.) Then a transformation is applied—in this case, a component called the TraxTransformer applies an XSL stylesheet to the incoming data. Finally, the HTMLSerializer writes data out to a browser client.

Though simple, the example above is common. If you set up this pipeline, create the XML and XSL files, Cocoon will happily serve the file contents according to the XSL instructions when you point your browser to your Cocoon application's index.html.

You might be wondering how all this relates to Java development. Java is vital when it comes to Cocoon's processing layer, represented by Figure 1's middle box. This processing layer, the heart of any Cocoon-based application, is where you apply logic to do something intelligent with the input data and get your desired output. While plenty of situations will arise where your pipelines are as simple as the example above, when you get into serious Web application development, you will need to apply logic to your data flow.

In Cocoon, you can implement logic in four main ways:

  • Using components called transformers: They do exactly what their name implies: they transform incoming data according to the rules they are given. The classic example is the TraxTransformer, which you can see in action in the pipeline above.
  • In the pipeline using various components that help choose the correct processing path based on various request/session/URI settings.
  • In the pipeline based on stock or custom Java-processing units called actions.
  • Using input files that mix Java and content—these are called Extensible Server Pages (XSPs).

This article covers this list's last two approaches: XSPs and actions. If you develop with Cocoon to any extent, you'll end up using them and probably liking them. Plus, you'll be happy to know that in both cases, you are essentially programming within a servlet context. More correctly, both components (in fact, all Cocoon components) have access to request, response, session, and context objects. Much of the logic you implement interacts with these objects in some way.

Let's look at XSPs and actions individually. If you haven't yet, you'll need to download the latest Cocoon distribution, now 2.0.3. Getting Cocoon up and running reaches beyond this article's scope. The easiest way to start is to go to http://xml.apache.org/cocoon/, download the latest official release of Cocoon, install it in a servlet container like Apache Tomcat (4.0.4 is recommended), and start playing. Read the installation instructions carefully. The documentation on the Cocoon Website is getting better by the day, so bookmark it. The Cocoon users mailing list is also well stocked with people willing to help you get started. Note that if you follow the default installation instructions for Tomcat, you point your browser to http://localhost:8080/cocoon/index.html to access the Cocoon Web application. This article's examples assume you have this basic setup and a URI base of http://localhost:8080/cocoon.

eXtensible Server Pages

XSPs are an innovation of the Cocoon project. You can compare them to JSPs (JavaServer Pages) because they mix logic and content and can import functionality via taglib-like files called logicsheets. XSPs represent a pipeline's start; that is, they actually convert on the fly into generators that then produce the data for the rest of the pipeline to use.

Let's start with a simple example, called sample1.xsp:

<?xml version="1.0"?>
<xsp:page language="java" xmlns:xsp="http://apache.org/xsp">
<xsp:logic>
  Date now = new Date();
  String msg = "Boo!";
</xsp:logic>
<content>
 <title>Welcome to Cocoon</title>
 <paragraph>
   This is an XSP. You can see how we it contain both logic (inside the 
<xsp:logic> tags) and content. In the logic block above, we created 
a Date object whose value is <xsp:expr>now</xsp:expr>. Oh, we 
also had a 
special message for you: <xsp:expr>msg</xsp:expr>
 </paragraph>
</content>
</xsp:page>

First note that this document's root tag is <xsp:page>. This tag defines the XSP's language (either Java or JavaScript) and lists the namespaces of all the logicsheets being used. (When you see logicsheet, think taglib—I'll discuss this concept more later.) Next comes a <xsp:logic> block in which we set two Java variables. These blocks—you can have more than one—can appear anywhere you need them and can contain all sorts of Java code. Finally, we have our content, starting with the root user tag, which, in our case, is <content>. Inside this content, however, we can access the variables we set at the beginning using the special tag <xsp:expr>.

Remember, an XSP is actually a generator. Cocoon turns it into a Java source file, compiles it, and then executes it. (If you want to see an XSP's complete Java source file, look under your servlet container's work directory. If you use Tomcat 4.0.4, for example, this file will typically be under a directory like $CATALINA_HOME/work/Standalone/localhost/cocoon/cocoon-files/org/apache/cocoon/www.) The XML data produced during execution then passes to the rest of the pipeline's components.

To see this XSP in action, let's create the following pipeline:

   <map:pipeline match="*.xsp">
     <map:generate type="serverpages" src="examples/{1}.xsp"/>
     <map:serialize type="xml"/>
   </map:pipeline>

Here, we use a special generator, the ServerPagesGenerator, to process our simple XSP. Rather than doing anything with the data, we simply return it to the client in raw XML form. Note the use of the special {1} variable reference: it refers to the substitution value indicated by the wildcard character at the start of the pipeline. In other words, if we point our browser to sample1.xsp in our Web application, the value of {1} will be sample1, since {1} is substituted for the wildcard character *. This feature results in a more generic pipeline and will serve for the rest of this article's XSP examples.

With an XML-aware browser like Internet Explorer, we will see sample1.xsp's output shown in Figure 2.

Figure 2. XML output from sample1.xsp. Click on thumbnail to view full-size image.

Remember that XSPs, like most Cocoon components, have access to request, response, session, and context objects. These objects are actually Cocoon encapsulations of HttpServletRequest, HttpServletResponse, HttpSession, and HttpServletContext and are called Request, Response, Session, and Context, respectively. The Cocoon versions provide access to most of the methods in the official versions.

XSPs prove especially useful for retrieving data from a database. When you think about it, database data makes great XML data, since it is naturally organized into rows and columns. However, JDBC (Java Database Connectivity) does not make for economical code. XSPs, on the other hand, can make your life much easier when retrieving data, thanks to the ESQL logicsheet. Besides hiding the gory JDBC details, the ESQL logicsheet allows you to encapsulate your rows and columns with custom tags. You can implement nested queries and run database update commands as well.

Suppose we want to store a list of Cocoon resources in a database table. First, we define the table and then use an XSP to retrieve the rows found when a user searches by keyword. Later, we'll build a form to add new rows.

The table definition and rows are shown below. I use MySQL, so please make the appropriate DDL (data description language) changes if you use a different database. In case you haven't, you must configure a connection pool in Cocoon for your database. See "Sidebar 1: Databases and Cocoon" for more information.

use test;
create table Resources (
ResourceURL   varchar(255) not null,
ResourceName  varchar(64) not null
);
insert into Resources values ('http://xml.apache.org/cocoon', 'Cocoon Home Page');
insert into Resources values ('http://radio.weblogs.com/0103021/stories/2002/02/14/cocoonPortalFirstLook.html', 'Cocoon portal - first look');
insert into Resources values ('http://www.galatea.com/flashguides/cocoon-tips-2.xml', 'Cocoon 2.0 Tips and Tricks');
insert into Resources values ('http://www.jboss.org/online-manual/HTML/ch14s07.html', 'Deploying Cocoon 2 in JBoss');
insert into Resources values ('http://www.galatea.com/flashguides/apache-tomcat-cocoon-2-win32.xml', 'Integrating Apache, Tomcat 3.2.x and Cocoon 2.0 on Windows');
insert into Resources values ('http://www.galatea.com/flashguides/tomcat-cocoon-42-unix.xml', 'Integrating Tomcat 4.0.x and Cocoon 2.0 on Unix');
insert into Resources values ('http://www.galatea.com/flashguides/tomcat-cocoon-42-win32.xml', 'Integrating Tomcat 4.0.x and Cocoon 2.0 on Windows');
insert into Resources values ('http://www.cocooncenter.de/cc/documents/resources/navigation/index.html', 'Creating a Navigation Menu');
insert into Resources values ('http://www.cocooncenter.de/', 'Your Guide to Apache Cocoon');
insert into Resources values ('http://www.cocooncenter.de/cc/documents/resources/logicsheet/index.html', 'Logicsheet Development');

With our table built and Cocoon properly configured, we can write a simple XSP:

<?xml version="1.0"?>
<xsp:page language="java" 
 xmlns:xsp="http://apache.org/xsp"
 xmlns:esql="http://apache.org/cocoon/SQL/v2">
<xsp:logic>
  String keyword = request.getParameter("value");
</xsp:logic>
<content>
 <title>Search results</title> 
 <esql:connection>
  <esql:pool>resources</esql:pool>
  <esql:execute-query>
   <esql:query>select * from Resources where ResourceName like
    '%<xsp:expr>keyword</xsp:expr>%'
   </esql:query>
   <esql:results>
    <resources>
     <esql:row-results>
      <resource>
       <esql:get-columns/>
      </resource>
     </esql:row-results>
    </ resources >
   </esql:results>
  </esql:execute-query>
 </esql:connection>
</content>
</xsp:page>

Look at the namespace declarations in the <xsp:page> tag. Any time you use a logicsheet in an XSP, you must declare the namespace as defined in the logicsheet. You can find these definitions in the WEB-INF/cocoon.xconf file in the Cocoon webapp directory. If you guessed that the XSP namespace declaration means there is an XSP logicsheet, you are right. All XSPs, in fact, implement at least the XSP logicsheet. Before the XSP turns into a Java source file, an XSLT process applies the logicsheets (which are actually just XSL files) to create Java code.

Therefore, all the ESQL tags you see in this example turn into the JDBC calls we all know and love. But aren't these tags simpler than writing all that JDBC code? Note the <esql:pool> block; that refers to a predefined database connection pool defined in the same WEB-INF/cocoon.xconf file. I called my pool resources, but you can call yours anything you like. Note too that we have surrounded the result set with the <resources> tag and each row therein with a <resource> tag. That allows you to easily write a stylesheet that turns the XML into something your browser understands. We didn't define any column-specific tags. By using the <esql:get-columns/> tag, Cocoon will write out each column value within tags created using the column names.

Let's look for a moment at the actual SQL query which, as you can see, is built dynamically. At the top of the XSP, we set a variable keyword to the value of the request parameter value. This would most likely be set when a search form GETs or POSTs to the XSP.

Let's test the above example. Again, since XSL transformation is not the focus of this article, we only look at the XML coming out of the XSP, which Figure 3 illustrates.

Figure 3. XML Output from sample2.xsp. Click on thumbnail to view full-size image.

Since this example is a fairly simple, let's complicate it a bit. Suppose the form lets the user submit an email address, to which the search results will return. "OK," you're thinking, "I remember something about the JavaMail package, I suppose we can do that." Well, before you dust off your JavaMail API documentation, check out the listing below:

<?xml version="1.0"?>
<xsp:page language="java" 
 xmlns:xsp="http://apache.org/xsp"
 xmlns:esql="http://apache.org/cocoon/SQL/v2"
 xmlns:sendmail="http://apache.org/cocoon/sendmail/1.0"
 xmlns:xsp-request="http://apache.org/xsp/request/2.0"
>
<content>
<xsp:logic>
  String keyword = <xsp-request:get-parameter name="value"/>;
  String emailBody = "";
  String emailAddr = <xsp-request:get-parameter name="email"/>;
</xsp:logic>
 <title>Search results</title> 
 <esql:connection>
  <esql:pool>resources</esql:pool>
  <esql:execute-query>
   <esql:query>
    select * from Resources where ResourceName like 
    '%<xsp:expr>keyword</xsp:expr>%' order by ResourceName
   </esql:query>
   <esql:results>
    <resources>
     <esql:row-results>
      <resource>
       <xsp:logic>
         emailBody += <esql:get-string column="ResourceName"/>;
         emailBody += ", " + <esql:get-string column="ResourceURL"/> + "\n";
       </xsp:logic>
       <esql:get-columns/>
      </resource>
     </esql:row-results>
    </resources>
   </esql:results>
  </esql:execute-query>
 </esql:connection>
 <xsp:logic>
   if (emailAddr != null) {
    <sendmail:send-mail>
     <sendmail:charset>ISO-8859-1</sendmail:charset>
     <sendmail:smtphost>MYSMTPHOST</sendmail:smtphost>
     <sendmail:from>MYFROMADDRESS</sendmail:from>
     <sendmail:to><xsp:expr>emailAddr</xsp:expr></sendmail:to>
     <sendmail:subject>Cocoon Search Results</sendmail:subject>
     <sendmail:body><xsp:expr>emailBody</xsp:expr></sendmail:body>
    </sendmail:send-mail>
   }
 </xsp:logic>
</content>
</xsp:page>

Isn't that nice? A few extra tags, referencing the sendmail logicsheet, and we have email capability in our application. In this example, we add each row of the query result set to the variable emailBody, which constitutes the email content. Then, assuming the user provided an email address as a request parameter, we fire off an email (note that you'll have to provide your own SMTP (Simple Mail Transfer Protocol) host name and from address). Cocoon knows to process the tags in the sendmail namespace with the sendmail logicsheet, because that namespace is declared in the <xsp:page> tag. Looking at that declaration, you'll see I added another namespace as well, xsp-request. The xsp-request logicsheet provides wrappers for Request's common methods. Although accessing this object directly doesn't differ functionally from accessing it via the xsp-request logicsheet, purists may prefer to use logicsheet tags rather than Java code.

Before you can run this example yourself, you must enable the sendmail logicsheet in Cocoon, which, for some unknown reason, is absent by default. Logicsheets are defined in cocoon.xconf, the Cocoon configuration file that lives in the Web application's WEB-INF directory. Open this file with your favorite editor and find the tag <target-language name="java">. In this block, you'll find all the other logicsheets defined. After the last one (the SOAP logicsheet), add the following:

<builtin-logicsheet>
  <parameter name="prefix" value="mail"/>
  <parameter name="uri" value="http://apache.org/cocoon/sendmail/1.0"/>
  <parameter name="href" value="resource://org/apache/cocoon/components/language/markup/xsp/java/sendmail.xsl"/>
</builtin-logicsheet>

This definition associates the namespace http://apache.org.cocoon/sendmail/1.0 with the stylesheet sendmail.xsl, which is already included in the Cocoon JAR. Depending on what servlet container you use, to use JavaMail, you also might have to make some JARs available to Cocoon—read "Sidebar 2: Email and Cocoon" for more information. Once you've installed the correct JARs, assuming you need them, bounce your servlet container and try the page.

More logicsheets are available in Cocoon for your enjoyment. One rather sexy one is the SOAP logicsheet, which lets Cocoon talk to SOAP servers (actually, Cocoon itself can function as a SOAP server in the 2.1 development version). But time to move on to actions.

Actions

Actions are powerful little work units you can place anywhere in your pipelines. Think of actions as small self-contained machines that take certain kinds of input data, do something intelligent, and return HashMap objects (or null, which is also a valid return type). Unlike components like generators, transformers, and serializers, actions have nothing to do with the actual XML content—they implement logic in the pipeline. Learning about actions involves understanding something about pipeline parameters: sometimes a pipeline's components must exchange data. Of course, the XML content itself is passed around as SAX events; but I'm talking about passing around values needed for component functionality.

There are two types of pipeline parameters: input and output. Input parameters are identified by one or more <map:parameter> tags immediately following the component declaration line. They serve to give the component one or more values that affect its operation. Two components, matchers and actions, are capable of providing output variables that components nested below them can access. These output parameters are set as HashMap objects and can be referenced by the key name enclosed in curly braces ({ }). Any pipeline automatically has at least one HashMap—provided by the matcher, which is the start of every pipeline. We've been using that object in the pipeline that handles our XSPs. The reference {1} that we use to find the right XSP refers to the value in the HashMap with the key of 1.

Cocoon comes with a number of built-in actions. One of these authenticates a user against a database table. Suppose we want to protect our Cocoon resources page and allow the user to access it only when he or she has been authenticated. We'll store the userids and passwords in a database and use DatabaseAuthenticationAction to handle the login validation. The DatabaseAuthenticationAction requires that we provide an XML descriptor file that indicates what table and columns to authenticate against. Our sample descriptor file is shown below:

<?xml version="1.0" encoding="UTF-8"?>
<auth-descriptor>
  <connection>resources</connection>
  <table name="Users">
    <select dbcol="USER_NAME" request-param="userid" to-session="userid"/>
    <select dbcol="USER_PASSWORD" request-param="userpwd"/>
  </table>
</auth-descriptor>

The above file says that authentication will occur against a table called Users, which is found in the resources database pool. Two columns will match against the incoming request parameters: column USER_NAME must match with parameter userid, and column USER_PASSWORD must match with parameter userpwd. If the validation succeeds, the value of the userid request parameter will be made available in the session as an attribute called userid (the meaning of the to-session attribute).

When you use an action in a pipeline, it must be defined inside a <map:actions> block, which lives at the end of the <map:components> block in the sitemap. Our action's definition looks like this:

 <map:components>
  <!--   all other component definitions go here   -->
  <map:actions>
   <map:action name="authenticator"
      src="org.apache.cocoon.acting.DatabaseAuthenticatorAction"/>
   <!--   other actions definitions go here   -->
  </map:actions>
 </map:components>

Once defined, we can now use the action in the pipelines that handle our protected area. We'll define three pipelines for this area:

   <map:match pattern="protected/login.html">
    <map:read mime-type="text/html" src="secret/login.html"/>
   </map:match>
   <map:match pattern="protected/login">
     <map:act type="authenticator">
      <map:parameter name="descriptor" value=" secret/auth-info.xml"/>
      <map:redirect-to uri="index.html"/>
     </map:act>
     <map:redirect-to uri="login.html"/>
   </map:match>
   <map:match pattern="protected/*">
    <map:match type="sessionstate" pattern="*">
     <map:parameter name="attribute-name" value="userid"/>
    <map:match pattern="protected/*.html">
      <map:read mime-type="text/html" src=" secret/*.html"/>
    </map:match>
    <map:match pattern="protected/*.xsp">
       <map:generate type="serverpages" src=" secret/{1}.xsp"/>
       <map:serialize type="xml"/>
     </map:match>
    </map:match>
    <map:redirect-to uri="login.html"/>
   </map:match>

The first pipeline simply provides a login form—since it is already an HTML file, we don't need transformation. The second pipeline handles the actual login and is the form action of login.html. The third action handles our protected content, which I'll discuss below. First, let's review how the login works.

DatabaseAuthenticationAction will validate a login using the information in the descriptor file. How will we know whether the validation succeeded? Well, here's a neat trick about actions: If they return a valid HashMap object, then everything inside the <map:act> block will execute. But, if the action returns null, then execution will fall outside the block. That explains the two redirects we have in the pipeline for protected/login; one sends the successfully authenticated users to our protected area, while the other redirects back to the login page.

The way the protected/* pipeline is set up illustrates another example of sitemap logic. Here, we have several nested matchers, but the second one is of type sessionstate. This matcher is actually the WildcardSessionAttributeMatcher, which matches the session attribute defined in the <map:parameter> tag with the defined pattern. In this case, since we know that DatabaseAuthenticationAction sets a session attribute called userid, if the authentication succeeds, we only need to check for the userid attribute. If it is absent, we assume the user is not authenticated and execution falls out to the redirection at the end of that pipeline. If it is present, then execution continues to the steps within that block, which, in this case, defines two pipelines for our protected area.

Something else I'd like to point out: You can see how most of this protected area's logic is encapsulated in the sitemap via the three pipelines we defined. This makes this logic's implementation nice and neat, fairly readable, and certainly easy to maintain. Note too how the pipeline for the pattern protected/* comes last. If it didn't, then Cocoon would be able to match protected/login to it, and our login would never process!

I'll leave it to you to define a simple login page that GETs or POSTs the userid and userpwd variables to protected/login's form action. The code for the protected/index.html page is shown below. Note that all our files—login page, descriptor file, and search XSP (I renamed sample3.xsp to search.xsp)—are in a folder called secret:

<html>
<head>
 <title>Welcome to the protected area</title>
</head>
<body>
 <center>
  <h3>
    Welcome to the protected area.
    Now, try a search in our list of Cocoon resources.
  </h3>
 </center>
 <form action="search.xsp" method="post">
  <table>
   <tr>
    <td align="right">Search keyword:</td>
    <td align="left">
     <input type="text" name="value"/>
    </td>
   </tr>
   <tr>
    <td align="right">Optional email:</td>
    <td align="left">
     <input type="text" name="email"/>
    </td>
   </tr>
   <tr>
    <td colspan="2" align="center">
     <input type="submit" value="Search"/>
    </td>
   </tr>
  </table>
 </form>
 <br/>
 <p><small><i>
  PS: Be sure not to tell anyone about this!
 </i></small></p>
</body>
</html>

If you like the way this action works, check out the other 26 built-in actions that come with Cocoon. Some do various database operations, a couple interact with the session, one sends email, and another helps validate forms. But being the cutting edge developers that we are, let's write our own action so you can better grasp the concept.

We'll write an action that inserts a new resource into a Resources table. (Users familiar with Cocoon might ask why DatabaseAddAction is not used. Besides the fact that we're doing a bit more than that action accomplishes, the main point here is to illustrate how to roll your own actions.) Again, I'll assume you can code a quick HTML page that POSTs two variables to the pipeline: name and url. Our action will retrieve these parameters from the Request object, insert a row into our table, and return a HashMap object with the two fields just in case a subsequent component wants to use them:

package test;
import org.apache.avalon.excalibur.datasource.DataSourceComponent;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.ComponentSelector;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.avalon.framework.component.Composable;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.Session;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.acting.AbstractAction;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
public class AddResourceAction extends AbstractAction implements ThreadSafe, Composable, Disposable
{
    protected ComponentSelector dbselector;
    protected ComponentManager manager;
    public void compose(ComponentManager manager) throws ComponentException {
        this.dbselector = (ComponentSelector) manager.lookup(DataSourceComponent.ROLE + "Selector");
    }
    protected final DataSourceComponent getDataSource(String pool) throws ComponentException {
        return (DataSourceComponent) this.dbselector.select(pool);
    }
    public Map act( Redirector redirector, SourceResolver resolver, 
                                     Map objectModel, String 
source, Parameters param )
        throws Exception
    {
        Request req = ObjectModelHelper.getRequest(objectModel);
        Session ses = req.getSession(true);
        String poolName = param.getParameter("pool");
             String resourceName = req.getParameter("name");
             String resourceUrl = req.getParameter("url");
             if (poolName == null) {
                 getLogger().error("Missing a pool name");
                 return null;
             }
             if (resourceName == null || resourceUrl == null) {
                 getLogger().error("Missing input parameters");
                 return null;
             }
        Map map = new HashMap();
            DataSourceComponent dataSource = 
getDataSource(poolName);
             Connection conn = null;
             boolean status = false;
        
                 try {
                 conn = dataSource.getConnection();
                 Statement stmt = conn.createStatement();
                 String cmd = "insert into Resources values ('" + 
                          resourceName + "', '" +
                          resourceUrl + "')";
                 stmt.executeUpdate(cmd);
    
                 map.put("resource-name", resourceName);
                 map.put("resource-url", resourceUrl);
                 
                 getLogger().debug("Resources insert completed by user 
" +
                         ses.getId());
                 status = true;
                 stmt.close();
             } catch (Exception e) {
                 getLogger().error("Stmt failed: ", e);
             } finally {
                 try {
                         if (conn != null) conn.close();
            } catch (SQLException sqe) {
                getLogger().warn("Error closing the datasource", sqe);
            }
        }
                          
             if (!status) return null;
        return(map);
    }
    public void dispose() {
        this.manager.release(dbselector);
    }
}

There's a lot in here to digest, especially if you are unfamiliar with Cocoon's architecture. We'll go step by step. To start, any Cocoon action's main method is act(). Cocoon will call this method when your action is used in a pipeline, passing in various objects you might need. In this case, act() retrieves the request parameters, retrieves a connection from the pool named by an input parameter, executes the insert, fills the HashMap object, and returns it.

Our action starts by getting the Request object from the ObjectModelHelper component. From this object, we can achieve access to the session as well as the input parameters. I'm only getting the Session object here for illustration purposes and to show that you can interact with it as you would in a servlet or JSP.

Remember when I talked about input parameters to sitemap components? This action requires one such parameter, pool; it will tell us which database connection pool to use. If this parameter is absent, the action will return null and an error will be logged (the getLogger() method is inherited from AbstractAction). With the pool name, we can get a connection from the requisite pool. An Avalon component called Excalibur handles connection pooling in Cocoon. If you're unfamiliar with Avalon, it serves as a fundamental part of Cocoon's architecture. Avalon is a project within Apache's Jakarta Project. Find out more from http://jakarta.apache.org/avalon/. Also from Avalon: the Composable and Disposable interfaces that our action implements, which are responsible for the compose() and dispose() methods.

Turning back to the code above, the insert statement is straightforward JDBC programming. After a successful insert, we log a quick success message that includes the session ID. Logging in Cocoon deserves its own article, but if you define the action as I do below, any log messages from this action will show up in your Web application's WEB-INF/logs/sitemap.log file. Finally, we set both input parameters in the Map object—since they are in the request, this is redundant, but it illustrates this objects' usage.

Let's look at this action's sitemap definition. As before, we must first define the action in the sitemap's <map:components> section. We will therefore enhance our earlier sitemap fragment as follows:

 <map:components>
  <!--   all other component definitions go here   -->
  <map:actions>
   <map:action name="authenticator"
      src="org.apache.cocoon.acting.DatabaseAuthenticatorAction"/>
   <map:action name="add-resource" src="test.AddResourceAction"
     logger="sitemap.action.AddResourceAction"/>
   <!--   other actions definitions go here   -->
  </map:actions>
 </map:components>

The pipeline definition that uses this action looks like this:

<map:match pattern="addresource">
 <map:act type="add-resource">
  <map:parameter name="pool" value="resources"/>
  <map:read mime-type="text/html" src="examples/confirmation.html"/>
 </map:act>
 <map:read mime-type="text/html" src="examples/addresource.html"/>
</map:match>

You can see that we supply the action with the expected "pool" input parameter via the <map:parameter> tag immediately after the <map:act> line. Assuming that everything went smoothly, the action will return a valid Map object and a confirmation page will display. If the action returns null, the user will see the form page. If you haven't yet set up your version of Cocoon for your particular database, check out "Sidebar 1: Databases and Cocoon" for more information.

If you are wondering how we compile actions, see the scripts below:

For Windows:

@echo off
set JAR_DIR=%CATALINA_HOME%\webapps\cocoon\WEB-INF\lib
set CLASS_DIR=%CATALINA_HOME%\webapps\cocoon\WEB-INF\classes
set CP=%JAR_DIR%\avalon-excalibur-vm12-20020705.jar
set CP=%CP%;%JAR_DIR%\avalon-framework-20020627.jar
set CP=%CP%;%JAR_DIR%\cocoon-2.0.3.jar
set CP=%CP%;%JAR_DIR%\logkit-20020529.jar
javac -d %CLASS_DIR% -classpath %CP%;%CLASSPATH% %1%

For Unix:

export JAR_DIR=$CATALINA_HOME/webapps/cocoon/WEB-INF/libexport CLASS_DIR=$CATALINA_HOME/webapps/cocoon/WEB-INF/classes
export CP=$JAR_DIR/avalon-excalibur-vm12-20020705.jar
export CP=$CP:$JAR_DIR/avalon-framework-20020627.jar
export CP=$CP:$JAR_DIR/cocoon-2.0.3.jar
export CP=$CP:$JAR_DIR/logkit-20020529.jar
javac -d $CLASS_DIR -classpath $CP:$CLASSPATH 

If you use a version of Cocoon other than 2.0.3, you might have to slightly modify some of these jar names. The compiled classfile is placed in

WEB-INF

's classes directory. Once there, you'll need to restart the servlet container so Cocoon can pick it up in its

CLASSPATH

.

If you point your browser to http://localhost:8080/cocoon/addresource, you'll see the input form. After the form submits and, assuming the insert operation succeeded, you'll get a confirmation page. If, however, you get the input form again, something went wrong. Check WEB-INF/logs/sitemap.log and look for entries starting with AddResourceAction. The error messages should tell you what went wrong.

How to use XSPs and actions effectively

XSPs and actions represent two different ways of implementing logic in Cocoon. The choice of which to use is largely a question of what you have to do and what you feel more comfortable with. XSPs are great for retrieving or creating structured data. With both logicsheets and judicious use of the XSP tags themselves, you can customize the data to whatever extent you need. I should point out, however, that you can also do customization downstream from the XSP with an XSL transformation if you feel more proficient with XSL than Java or if it better fits your needs.

Actions prove useful when you must implement logic outside data flow—logic that might control the data or processing flow but does not (generally) interact with the data itself. We've seen authentication and a database operation as two examples of this. As with XSPs, you have alternatives to actions, namely, matchers and selectors. In the end, the choice is yours. There are no hard and fast rules about using XSPs versus actions or versus anything else. The best approach is to pick what you feel most happy with, code away, and see what happens. Chances are, as you become more proficient in Cocoon, you'll develop your own set of rules.

However, XSPs are problematic in one respect: their mix of content and logic. One fundamental principle of Cocoon's design is that content, logic, and presentation are fairly well separated so that different aspects of the application can be developed and maintained without interfering with each other. So, you might ask, don't XSPs violate this principle, called Separation of Concerns? In fact, yes, but whether that is good or bad depends on how you implement the logic. As with JSPs, with XSPs, you can also either neatly separate your logic and data or happily mix them until you have a real mess. If you choose the neat and tidy option, I recommend the following: First, use logicsheets whenever possible. Write your own, if you can. Remember how a few simple tags in the ESQL logicsheet hide all the JDBC details? You can do the same for you own logic details. Second, take advantage of one of Cocoon's great features: the multiple approaches to certain tasks. You can also do database selects, for example, with the SqlTransformer. Other times, you might be able to use components like selectors, matchers, or actions to implement certain kinds of decision-making logic. This article is too short to illustrate that, but the more you work with Cocoon, the more you'll be able to do things as simply and cleanly as possible. Finally, if you do need or want to embed Java logic in your XSPs, try to keep things as structured as possible—keep your <xsp:logic> blocks to a minimum and try not to intersperse them constantly with your content tags. If you do, either you or your successor will have a devil of a time maintaining your files later.

Cocoon quickly hatches Web apps

I hope this overview of XSPs and actions gives you some idea of the possibilities that Cocoon offers. As you have seen, these components provide rather well defined areas in which to implement your own logic. The built-in logicsheets and actions that come with Cocoon can help you do things that you would have to code from scratch in another framework. The advantage is that you can get your Cocoon-based application up and running much faster. And when you couple this with all the other powerful components that Cocoon offers—like matchers, selectors, generators, transformers, serializers, and readers—you can build yourself quite powerful Web applications. If you like what you've seen, grab yourself a copy and start coding! Chances are, you'll be hooked.

Lajos Moczar is president of Galatea IS, a consulting company specializing in Apache, Tomcat, and Cocoon consulting, support, and training services. He discovered Cocoon three years ago, after writing his own XML publishing engine. Since then, he has used Cocoon on many projects and has become a frequent contributor on the Cocoon users mailing list. This spring, he put his expertise to work by offering the first full-length Cocoon training classes. He just wrapped up a book that he is co-authoring with Jeremy Aston, entitled Cocoon Developer's Handbook (SAMS), scheduled for publication in December.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more