Apache » Cocoon »

  Cocoon 2.2
   homepage

Cocoon 2.2

Modularize Cocoon apps (Using blocks)

In the basic tutorial "Your first Cocoon application using Maven 2" you created your first block. Cocoon 2.2 introduced the concept of blocks because it should help you to split your application into smaller parts. This increases the maintainability and reusability by orders of magnitude.

In this tutorial you will

  1. create a second block
  2. connect one block with another one
  3. use a pipeline fragement available in one block from within another block

Create a second block

For this purpose move into the getting-started-app directory and use the Maven 2 archetype command again:
mvn archetype:generate -DarchetypeCatalog=http://cocoon.apache.org
First choose the archetype:
Choose archetype:
1: remote -> cocoon-22-archetype-block-plain (Creates an empty Cocoon block; useful
 if you want to add another block to a Cocoon application)
2: remote -> cocoon-22-archetype-block (Creates a Cocoon block containing some small
 samples)
3: remote -> cocoon-22-archetype-webapp (Creates a web application configured to
 host Cocoon blocks. Just add the block dependencies)
Choose a number:  (1/2/3): 2
Then respond to the other questions as follows:
Define value for groupId: : com.mycompany
Define value for artifactId: : myBlock2
Define value for version:  1.0-SNAPSHOT: : 1.0.0
Define value for package: : com.mycompany.myBlock2

The result is a second Cocoon block called myBlock2.  You should find the directory structure of your application now looks as follows:

getting-started-app
 +-myBlock1
 |  +-pom.xml
 |  +-src
 |     +-[...]
 +-myBlock2
    +-pom.xml
    +-src
       +-[...]

Move into the myBlock2 folder and execute the following command:

mvn install eclipse:eclipse

This builds and copies the second block into your local Maven repository so that other dependent blocks and projects can see it and then creates the necessary files to allow you to import the block as a project in Eclipse.

Connect two blocks

Let's assume that you want to use a pipeline defined in myBlock2 from within myBlock1. You have to establish the connection between the two blocks. Edit getting-started-app/myBlock1/src/main/resources/META-INF/cocoon/spring/block-servlet-service.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:servlet="http://cocoon.apache.org/schema/servlet"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
  http://cocoon.apache.org/schema/servlet http://cocoon.apache.org/schema/servlet/cocoon-servlet-1.0.xsd">

  <bean id="com.mycompany.myBlock1.service" class="org.apache.cocoon.sitemap.SitemapServlet">
    <servlet:context mount-path="/myBlock1" context-path="blockcontext:/myBlock1/">
      <servlet:connections>
        <entry key="myBlock2" value-ref="com.mycompany.myBlock2.service"/>
      </servlet:connections>
    </servlet:context>
  </bean>

</beans>

Additionally, you have to record the fact that myBlock1 has a dependency on myBlock2 by editing myBlock1's Maven project descriptor (getting-started-app/myBlock1/pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<project>
  [...]
  <dependencies>
    [...]
    <dependency>
      <groupId>com.mycompany</groupId>
      <artifactId>myBlock2</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
  [...]
</project>

If you use the RCL goal of the Cocoon Maven plugin, you will also want to add the location of the new block to the configuration file of block1. This has the advantage that you can work on block2 and the changes take effect immediately. Open getting-started-app/myBlock1/rcl.properties and add

com.mycompany.myBlock1.service%classes-dir=./target/classes

com.mycompany.myBlock2.service%classes-dir=../myBlock2/target/classes
%exclude-lib=com.mycompany:myBlock2

Restart the servlet container by invoking

mvn jetty:run 

Now it's time to test connection. For that purpose add a pipeline to the sitemap of block1 (getting-started-app/myBlock1/src/main/resources/COB-INF/sitemap.xmap):

<?xml version="1.0" encoding="UTF-8"?>
<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
  <map:pipelines>
    [...]
    <map:pipeline>
      <map:match pattern="callingBlock2">
        <map:generate src="servlet:myBlock2:/spring-bean" type="file"/>
        <map:serialize type="xml"/>
      </map:match>
    </map:pipeline>
    [...]
  </map:pipelines>
</map:sitemap>

The file generator of this pipeline uses a special purpose protocol, the servlet protocol, to access a pipeline defined in the other block. If you enter http://localhost:8888/myBlock1/callingBlock2 shows the output of the spring-bean pipeline of myBlock2 but routed through myBlock1.
Though this is a "hello word" style example you might already imagine the power of this protocol, e.g. you can move all styling resources and pipelines to a particular block.

Use a pipeline fragment

The previous example showed how you can call a pipeline from another block. But here is even more you can do! A sitemap can also provide pipeline fragements that can be used by other blocks:

<?xml version="1.0" encoding="UTF-8"?>
<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
  <map:pipelines>
    <map:pipeline>
      <map:match pattern="callingTransformationService">
        <map:generate src="demo/welcome.xml"/>
        <map:transform type="servletService">
          <map:parameter name="service" 
           value="servlet:myBlock2:/myXsltTransformation-service"/>
        </map:transform>
        <map:serialize type="xml"/>
      </map:match>
    </map:pipeline>
  </map:pipelines>
</map:sitemap>

When the requests arrives at callingTransformationService pipeline, the generator produces SAX events of demo/welcome.xml. There is nothing special here. The interesting part comes with the following transformer of type servletService. It calls a transformation service which is provided by myBlock2.

Add this service to the sitemap of myBlock2 (getting-started-app/myBlock2/src/main/resources/COB-INF/sitemap.xmap). It consists of one XSLT transformation step:

<?xml version="1.0" encoding="UTF-8"?>
<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">
  <map:pipelines>
    <map:pipeline>
      <map:match pattern="myXsltTransformation-service">
        <map:generate src="service-consumer:"/>
        <map:transform src="myXsltTransformation.xslt"/>
        <map:serialize type="xml"/>
      </map:match>
    </map:pipeline>
  </map:pipelines>
</map:sitemap>

The generator uses the service-consumer protocol which initializes the service. Then the pipeline continues with an XSLT transformation myXsltTransformation.xslt, which has to be put into the same directory as the myBlock2 sitemap:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <html>
      <head>
        <title>Output of the XSLT transformation service.</title>
      </head>
      <body>
        Output of the XSLT transformation service.
      </body>
    </html>
  </xsl:template>
</xsl:stylesheet>

Point your browser at http://localhost:8888/myBlock1/callingTransformationService and see the output of the pipeline.

Note: Besides transformation services there are also generation and serialization services.