All documents and files included in this tour are Copyright (C) 1999-2004 by the Apache Software Foundation.
You can generate a printable single-page version of this tour here (requires an Internet connection for some example pages).
This supersonic tour of Apache Cocoon will give you a quick overview of what Cocoon is and does.
We won't go deep into any part of Cocoon, but will rather get an overall feel of what's in Cocoon.
The only parts that we will study in some detail are the following Cocoon components, which are becoming the mainstream way of using Cocoon:
If you're reading this documentation on paper and would like to study the application directly in Cocoon, you will find it under blocks with samples on the Cocoon samples page.
The complete source code of this tour is found under src/blocks/tour in the Cocoon source code tree. There is one directory with its own sitemap for each part of the tutorial, so that you can easily dig deeper in your areas of interest.
Note that (according to the lazyness is a virtue rule), most of the sitemap, flowscripts, XSLT transforms and other code excerpts are dynamically inserted into this documentation from the corresponding source files. This avoids having to copy and paste code and ensures that the code you see stays up to date.
Depending on who you ask, you might get different answers: Cocoon 2 has become a rather big project, or more precisely a rather big collection of modular components, and as such means different things to different people.
Today, more than 40 blocks are available to acquire, process and publish XML data, and to build interactive applications based on XML and java technologies. Cocoon does not contain millions of lines of codes, but rather reuses a lot of Open-Source tools and libraries, combining them in a very modular way.
In the beginning, Cocoon was only a web publishing framework, meant to convert different document types to HTML, PDF and similar formats for publishing.
Today, and especially with the Cocoon Forms and Flow components that we're going to discuss, Cocoon is becoming a full-blown web applications framework, covering a large set of use cases and targeting different styles of developers.
By covering the mainstream aspects of Cocoon as it stands at the beginning of 2004, this supersonic tour will help you get a feel of what Cocoon is about, and help you find your way in this big collection of components.
Here's a non exhaustive but typical list of Cocoon use-cases, including some weird ones to show Cocoon's flexiblity...
See also the list of Cocoon features at http://cocoon.apache.org/2.1/features.html
Cocoon is written in Java and based on the Apache Avalon components framework.
Modular blocks can be enabled by configuration when installing Cocoon, allowing you to have either a small and focused system, or a "kitchen sink" installation with all possible options.
Many of Cocoon's core components and blocks reuse Open-Source libraries, many from other Apache projects and some from outside the Apache Software Foundation.
The caching subsystem uses sophisticated yet very customizable caching algorithms to maximize performance. Although some components or blocks might use a lot of resources (for example when generating PDF documents or bitmapped images), the Cocoon core itself is very efficient and can be tuned to maximize performance under high loads.
Cocoon is distributed under the liberal Apache License which maximises its use while still providing protection.
The Cocoon project lives through its contributors and committers, a community which is recognized for being friendly and efficient. Discussions take place on the mailing lists and IRC channels, and conferences or informal mettings called GetTogethers happen in various places.
The current team comprises more than 40 active committers, of which about 20 are regularly active. Those committers and the wider Cocoon community contribute new components, bug fixes, tests and documentation, and provide user support to each other.
The main Cocoon web site is found at http://cocoon.apache.org and contains links to the project's mailing lists, CVS code repository and other resources.
An important source of information is the http://wiki.apache.org/cocoon site, a Wiki containing lots of information which completes (and often overlaps) the official documentation.
The samples provided with Cocoon will help to explore the functionality of the various blocks. The main website contains some documentation tracks which will help you analyze and understand the samples.
A question which often comes when starting with Cocoon is, "How do I implement my business layer?"
The short answer: it is up to you ;-)
More precisely, you could say that Cocoon leaves a lot of freedom regarding how to access databases and run business processes or apply business rules.
Similarly, people often ask about "best practices" with Cocoon -- that is, "What's the best way to implement X or Y?" While we generally don't have a set list of best practices, we can provide some simple guidance to help you along your way.
Here's a brief discussion of possible options. Some are real today and some are still - as we like to say here - in the pipeline.
Writing your business layer code inside Flowscript might be tempting after looking at this tour's examples, but please don't!
Flowscript is not meant for more than glue between pages and business code, and its design and future evolution will stay targeted to small glue modules.
As we'll see in our example, it is very easy to access Java objects from Flowscript code. Such objects do not necessarily need to know about Cocoon or Avalon classes, which means that legacy code could be easily integrated, provided there are no class libraries conflicts.
Working in this way however, prevents you from using any Avalon features like configuration, logging, and monitoring facilities.
As such, this is a suboptimal solution, but might be interesting for small applications where you don't want to learn too much about Avalon and Cocoon.
The next step would be to write first-class Cocoon components based on the Avalon framework, allowing your components to use all of the Avalon facilities, and if necessary to access Cocoon components directly.
In this case, your build system will be integrated with the Cocoon build system, and you will tailor your build to include only the required Cocoon blocks.
This is the way to go if you don't mind the tighter coupling between Cocoon, Avalon and your application, and if you are ready to learn these technologies in more detail. The benefits are largely worth it for serious applications.
Structuring your code in blocks, as is done inside Cocoon, will make integration easier and should help future migrations to newer versions of Cocoon.
If you're worried about coupling, RMI components might be an interesting option: in this case, only a small facade will be integrated in Cocoon, and your application will run in its own process with no risks of class library conflicts.
The downside is added complexity and a possible loss of performance, depending on your application patterns.
The next step towards decoupling would be to use REST or SOAP backends to communicate with your business layer, leaving you free to choose the language and framework of your choice to implement the backends. Interoperability with other systems can also be a big advantage in this case.
There are some SOAP helper components in Cocoon today, but we don't see a lot of discussions about them on the mailing lists, so we don't know if their use is widespread.
A good example of a REST backend is the XReporter database reporting framework (http://xreporter.cocoondev.org/).
Recent discussions about a possible integration of SOAP with Flowscript are promising. Being able to transparently access SOAP backends directly from Flowscript would make it possible to create forms-based frontends to SOAP services with a minimal amount of code. Stay tuned...
The multi-channel publishing subsystem of Cocoon is based on XML processing pipelines.
A pipeline consists of:
The are many types of Generators, Transformers and Serializers, but a typical pipeline could consist of:
The building and configuration of the pipelines is controlled by the sitemap, an XML document used by Cocoon to dynamically instantiate and activate pipelines.
The next pages show several examples using various components.
Our first pipeline uses the RequestGenerator to output the request attributes in XML.
Shown below is the result of a request to dump-request?param=xyz. The response is XML as specified by the serializer, and describes the request attributes and parameters, converted to XML by the RequestGenerator component.
Adding a transformer with map:transform and changing the serializer to HTML allows us to generate HTML out of the XML of the previous example.
You can also view the output here: request.html
You can chain several transformers in a pipeline. In this example we add some style to our HTML by adding an additional XSL transform.
View the output in your browser: styled/request.html
At the end of the pipeline, different Serializers can generate different output formats, provided they're fed the right data (i.e. the required XML elements and namespaces).
Let's generate a simple graph out of of our request parameters. If your have an SVG plugin in your browser you can view the result: red/request.svg or blue/request.svg. If you don't have an SVG plugin see below for the bitmapped version of the same image.
Here we show only the main template wich generates the SVG skeleton
Post-processing the SVG output allows us to generate JPEG (cyan/request.jpeg) or PNG (black/request.png) images, simply by configuring a different serializer at the end of the pipeline.
Note the use of the cocoon:/ protocol to re-use the previous pipeline as input: when a request to blue/request.png is received, the output of the blue/request.svg is used as the input of the first pipeline shown below.
Until now our pipelines have all started with the RequestGenerator. Let's see a different example, where we retrieve an external XML document via HTTP.
This example requires access to the http://codeconsult.ch/bertrand/index.rdf XML newsfeed.
View the result in your browser: xmlnews.html
Here we show only the conversion of rss:item elements
Through its many Serializers, Cocoon can transform XML data into many output formats, including, but not limited to:
We say not limited to, because we might have forgotten to list some, but also because it is fairly easy for a Java programmer to implement any output format by writing a new Serializer or extending an existing one.
The Hello world samples of Cocoon give simple examples of how these formats are generated.
Our simple example will let you input some text and create a PDF document out of it, using the PDFSerializer based on the Apache FOP project.
In this example we will generate a PDF document containing text received in the HTTP request parameters.
To run the example, submit this form, after changing the provided text if you want to:
If your browser has problems with PDF coming out of a form submit use this link instead.
The FOPSerializer requires a document in the XSL-FO vocabulary for input, and converts this to PDF, handling fonts, page layout, etc.
Our sitemap will then apply an XSL transform to the output of the RequestGenerator to convert it to XSL-FO, using request parameter values provided by the generator.
Errors in the XSL-FO document can happen when implementing such a transform, and to make debugging easier we use a view in the sitemap, to access the intermediate document before it is processed by the FOPSerializer.
Here are the view and pipeline definitions:
These definitions allow the intermediate XSL-FO document to be retrieved by adding the
cocoon-view=xsl-fo parameter at the end of the URL, for example:
../pdf-example/pdf-example.pdf?title=....&cocoon-view=xsl-fo
This transformation converts the RequestGenerator output in XSL-FO, inserting the value of the text and title request parameters in the output document:
Through the magic of continuations, the Cocoon Flow subsystem makes it easy to track the user's state when interacting with a web-based application.
A continuation saves the state of execution of the currently running Flowscript, and allows this state to be "resurrected" later on, typically when the user submits an HTML form that was sent with the cocoon.sendPageAndWait instruction.
This makes the typical web applications interactions much easier to manage, as the developer can write "linear" code for page flows, instead of having to keep track of the user's state manually. Our examples will show how little code is used to manage typical interactions.
Having to use JavaScript to write the Flow scripts might seem strange at first, but in practice only a few lines of Flow code will be required for a typical application, so this doesn't matter much. The reason is that JavaScript (through a modified version of the mozilla Rhino interpreter) is currently the only continuations-enabled language that can be distributed with Cocoon.
Adapted from Tony Collen's GettingStartedWithFlow page on the Cocoon wiki.
In this example, an HTML form is shown repeatedly, until the user guesses the correct number.
Start the game here.
At the sitemap level, Flowscript applications need four things:
Note that this already contains the declaration of the next example's Flowscript.
Here we use variables to allow any Flowscript function to be called, with a simple security restriction: the function name must start with the public_ prefix.
The maxValue parameter is used by our Flowscript public_startGuessNumber function, but doesn't hurt if it is passed to other functions (like in the next example).
This activates a Flowscript continuation when an URL ending with a continuation ID and the .continue suffix is received.
To be able to include data provided by Flowscript in our forms and views, we use the Flowscript-aware JXTemplateGenerator
Here's the pipeline
Which uses this resource to convert the page to HTML
And here's the JXTemplate component declaration
Here's the complete Flowscript code that drives the number-guessing game. (sorry about the lost indentation, the Slop block, which generates these listings, has been improved in the meantime).
The JXTemplateGenerator makes Flowscript variables (like the toGuess and hint attributes of the sendPageAndWait call above) accessible using a simple substitution syntax.
Here's the page definition for our number-guessing form. JXTemplate codes like ${cocoon.continuation.id}, will by replaced by values provided in the Flowscript sendPageAndWait function call.
We have now seen the complete code of our Flowscript application:
This example uses a simple two-page form to enter data for a simulated email message.
Start the example here.
Although a two-page form does not make sense for three fields when run from a desktop browser, this demonstrates how the Flow makes it easy to keep track of the user's "position" in the application flow. Just imagine you're working on a tiny mobile device for a minute.
We won't use Cocoon Forms here, but simply bind a JavaScript object to our form manually.
Notice how little code is needed to keep state while the user can freely move between the pages without losing data.
There's nothing new in the sitemap, our use of variables allows the exact same sitemap to be reused for both our Flow examples.
The only specific thing is the importing of the multi-page.js Flowscript, but this was already present for the previous example:
Here's the Flowscript which drives the multi-page form example:
0002 * Licensed to the Apache Software Foundation (ASF) under one or more
0003 * contributor license agreements. See the NOTICE file distributed with
0004 * this work for additional information regarding copyright ownership.
0005 * The ASF licenses this file to You under the Apache License, Version 2.0
0006 * (the "License"); you may not use this file except in compliance with
0007 * the License. You may obtain a copy of the License at
0008 *
0009 * http://www.apache.org/licenses/LICENSE-2.0
0010 *
0011 * Unless required by applicable law or agreed to in writing, software
0012 * distributed under the License is distributed on an "AS IS" BASIS,
0013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014 * See the License for the specific language governing permissions and
0015 * limitations under the License.
0016 */
0018 // Multi-page Flow example
0019 // Simple multi-page form, without using Cocoon Forms
0021 var date = new Packages.java.util.Date();
0023 // simulated email message data
0024 function MessageData() {
0025 this.sender = "you@somewhere.com";
0026 this.subject = "Type the subject here";
0027 this.text = "Type the text of your message here";
0028 }
0030 // page flow
0031 function public_startMultiPage() {
0032 var message = new MessageData();
0034 while(true) {
0036 // decide which page to show based on request parameters
0037 var page = "page1";
0038 if(cocoon.request.getParameter("action_send") != null) {
0039 break;
0040 } else if(cocoon.request.getParameter("action_page2") != null) {
0041 page = "page2";
0042 }
0044 // show form and wait for results
0045 cocoon.sendPageAndWait("multi-page/views/" + page, { "message" : message, "date" : date });
0047 // now for the boring part: copy form data into message
0048 // that's where Forms bindings would help
0049 var tmp = cocoon.request.getParameter("sender");
0050 if(tmp != null) message.sender = tmp;
0052 tmp = cocoon.request.getParameter("subject");
0053 if(tmp != null) message.subject = tmp;
0055 tmp = cocoon.request.getParameter("text");
0056 if(tmp != null) message.text = tmp;
0057 }
0059 // user selected "send", show message contents
0060 cocoon.sendPage("multi-page/views/result", { "message" : message });
0061 }
It is not more complicated than the previous example, but slightly longer due to the (boring) copying of request parameters into the message object. This boring part is where Cocoon Forms will come into play, by making it easier to bind data to values edited in a form.
By copying values into the message object after each submission, we allow the user to freely move between pages without losing data.
Nothing special in the form view, this is very similar to the previous example.
We're only using two submit buttons, one to move between pages and one to actually submit the data and go to the result page.
This is page 1. The page 2 is similar but shows the text field instead of the fields present on page 1.
In this example, java classes are used to calculate the area and perimeter of shapes. Various Java classes are instantiated and used from Flow.
Start the example here.
There's nothing new in the sitemap, our use of variables allows the exact same sitemap to be reused for both our Flow examples.
The only specific thing is the importing of the java-shapes.js flowscript, but this was already present for the previous example:
The java code (interface Shape, classes Rectangular, Square, Circle) is fairly trivial, and the computations could easily be done in javascript for such a simple case.
However, our goal is to show interactions between Flow and java classes, so you shouldn't pay too much attention to the java code, except to note that it has no knowledge of Avalon - our java objects are just POJOs: Plain Old Java Objects.
Here's the Flowscript which has three steps.
Shape selection
Here the user can select a shape.
The bean editor application uses Cocoon Forms, Pipelines and Flow to edit a simple data structure modeled by Java objects.
As such, it is a good example of how a Cocoon front-end can be used to manipulate business objects implemented in Java.
You will notice that the Java objects have nothing to do with Cocoon in our case. They are completely independent of any Cocoon libraries. This is one of the options for implementing the business layer of your application, useful when you have legacy business layer code to integrate.
Our Java beans are trivial and uninteresting: what we're looking for is an understanding of how Cocoon Pipelines, Forms and Flow play together with Java code, and for this a very simple application is certainly good.
After studying this application, you will see that very little code had to be written to implement an already capable system. As is often the case with a modular system like Cocoon, the hard part is not having to write a lot of code, but rather finding where to write the small amounts of code that are needed.
To keep it simple and focused, this application does just three things:
The application is obviously incomplete, but shows many interesting features of the Cocoon Forms, and their interplay with the Cocoon components that we already know.
To run the bean editor application, Cocoon must be able to load the required Java classes.
If you're running this tutorial from the standard Cocoon distribution this should be taken care of already, as the required classes are copied into the webapp/WEB-INF/lib directory automatically during the build.
When you start using your own Java classes with Cocoon, you'll have to take care of this yourself, usually by making sure the required jar files are copied in the right place.
For now, you can start the application from this link.
As already mentioned, our Java beans are dead simple and not very robust. The "database" is simply held in memory, loaded with simulated data at startup.
Here's the interface of the DatabaseFacade class, which is used by our Flowscript code to "talk" to the Java beans:
/** access the Database */
public static DatabaseFacade getInstance();
/** get our list of tasks
* @return a List of TaskBean objects */
public List getTasks();
/** get a single TaskBean */
public TaskBean getTaskBeanById(int id) throws Exception;
/** get this object's version */
public String getVersion();
Here's the interface of the TaskBean class, our main "task" object. It is basically a Java Bean with one read-only and three read-write properties.
public int getId();
public String getTaskName();
public void setTaskName(String m_taskName);
public String getAssignedTo();
public void setAssignedTo(String m_assignedTo);
/** @return a List of TaskCommentBean objects */
public List getComments();
/** @param c a List of TaskCommentBean objects */
public void setComments(List c);
Here's the TaskCommentBean interface:
public int getId();
public Date getDate();
public void setDate(Date m_date);
public String getComment();
public void setComment(String m_comment);
Here's a code excerpt showing how Flowscript code can access Java classes.
var db = Packages.org.apache.cocoon.samples.tour.beans.DatabaseFacade.getInstance();
...
list = db.getTasks();
Simple enough. The "official" way of accessing Java components in a Cocoon application would be to use the Avalon lookup mechanisms, but this wouldn't add much to our example so we took the easy way here.
Let's look at the implementation of the view/allTasks application page, which lists all tasks provided by the DatabaseFacade.
The scenario is the following:
Here's the first pipeline definition in sitemap, activated by the view/allTasks request:
This causes the above request to call the query_allTasks() Flowscript function (Flowscript listing here).
Later, the Flowscript will call the internal/generate-view/taskList pipeline to generate a view using the JXTemplateGenerator and the views/taskListView.xml page:
The following lines of Flowscript code implement the first two steps of our scenario, getting a List of TaskBean objects from the DatabaseFacade and passing it to the taskList pipeline.
0006 // Access java "database" facade object
0007 var db = Packages.org.apache.cocoon.samples.tour.beans.DatabaseFacade.getInstance();
...
0010 function query_allTasks() {
0011 list = db.getTasks();
0013 cocoon.sendPage("internal/generate-view/taskList", {
0014 title : "List of tasks",
0015 task : list,
0016 db : db
0017 });
As the taskList pipeline uses a JXTemplate generator, the corresponding
page will have access to the variables passed with the sendPage call.
The db object is also passed to the page, but it is only used to access its db.version field.
We're not using continuations here (there's no sendPageAndWait), Flowscript serves only as a thin layer of glue between our Java objects and our JXTemplate view page.
Here's the JXtemplate page that generates the taskList view, using the title, task and db variables supplied by the above Flowscript code:
Note the use of a <c:forEach> element, from the JXTemplate namespace, to iterate over members of the task collection.
Let's summarize what happened here:
The view/singleTask page (for example view/singleTask?taskId=2) is built in a similar way than the tasksList page, without requiring new definitions in the sitemap.
Shown below are the singleTask-specific portions of our code. Refer to the taskList page for the corresponding sitemap excerpts.
These methods query the java objects and call the appropriate pipeline for display:
0020 // Query a single TaskBean object and display it
0021 function query_singleTask() {
0022 id = cocoon.request.getParameter("taskId");
0023 bean = db.getTaskBeanById(id);
0024 displayTaskBean(id,bean);
0025 }
...
0038 // Display a single TaskBean
0039 function displayTaskBean(id,bean) {
0040 cocoon.sendPage("internal/generate-view/singleTask", {
0041 title : "Task #" + id,
0042 task : bean,
0043 selectedTaskId : id
0044 });
0045 }
Note that no error checking is done, for example for a missing taskId parameter. This would obviously be needed in a production application.
At this point, our application allows us to navigate in our TaskBean objects, and we're ready to start editing them.
To take advantage of the Cocoon Forms editing features, we need the following:
Note also that, while our example uses static XML files for these definitions, nothing prevents you from generating them dynamically using pipelines and the cocoon:/ protocol. If your application provides a central data dictionary, for example, it would be possible to generate at least simple versions of the required definition files automatically.
Let's look first at the pipeline definition for the edit/singleTask request:
What this does is to call the handleForm() function of the Form.js Flowscript library, telling it which of our own Flowscript function to call to edit the form, and indicating the locations of the Form Model (form-definition) and Form Binding documents.
Later, the Flowscript function shown below will call this pipeline:
Using the FormsTransformer to generate the appropriate HTML elements for our form's widgets.
Here's our Flowscript form editing function, called by the handleForm library function to edit our form:
0027 // Edit a single TaskBean object using Cocoon Forms
0028 function singleTaskEditor(form) {
0029 id = cocoon.request.getParameter("taskId");
0030 bean = db.getTaskBeanById(id);
0032 form.load(bean);
0033 form.showForm("internal/show-form/singleTask");
0034 form.save(bean);
0035 displayTaskBean(id,bean);
0036 }
Notice how simple and readable the code is - the magic happens behind the Cocoon Forms scenes, based on the Forms definitions shown below.
Here's the definition of our Form Model.
As you can see, the Forms subsystem uses widgets with labels and datatypes to define a Model.
If you omit the repeater element above, this is a fairly simple data model which represents our form's data. The repeater is used to model the 1-N relationship between our TaskBean and TaskCommentBean objects.
Strongly typed fields are an important features of Cocoon Forms, and provide many standard validation features which make our life easier.
This model is independent from the way the form is going to look in HTML (or WML, or XUL, or whatever), and also independent of the internal structure of our Java beans.
Here we use constants for the widget labels (field names), but the i18n transformer could be added to our pipeline to easily generate views and forms in multiple languages.
Here's the binding definition, which allows the Forms subsystem to automatically move data from our Form's internal model to our Java beans.
This looks simple enough: for example, we tell the Forms subsystem that the form's taskId field is mapped in readonly mode to the bean's id field.
The data mapping is triggered by the Flowscript form.load and form.save calls shown above.
For the taskId form field, the readonly flag makes the binding unidirectional. The value of the form field will not be copied to the bean when form.save is called.
Here's the template used by the FormsTransformer, which is activated by the above sitemap excerpt.