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
- create a second block
- connect one block with another one
- 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
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
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.