taming cocoon apachecon europe 2005 gianugo rabellino
TRANSCRIPT
Taming Cocoon
ApacheCon Europe 2005Gianugo Rabellino
Agenda
Introduction Installing Cocoon Dissecting Cocoon Management and configuration A sitemap tour Cocoon components and blocks Understanding continuations and flow Dealing with forms Patterns, best practices, pitfalls
Introduction: web developer nightmares
out.println(“<table border=\”0\”>\n\t<tr>\n\t\t” + “<td align=\”left\”><font size=\”+1\”>” + request.getParameter(“lousyjob”) + “Does your boss know you\'re typing HTML?” + “</td>\n\t</tr>\n</table>\n”);
Nightmare: managing HTML
Your IDE can't deal with HTML Dreamweaver can't deal with business logic Period. Oh, and JSPs suck. Big time.
“Separate concerns” - Edsger Dijkstra - 1974
Nightmare: handling state
HTTP is stateless (did you notice already?) Where am I now? Restart required Sessions == overpopulated mess of dictionaries
Nightmare: managing user input
Validation hell Conversion ordeal Binding torture The infamous Back button, oh my!
There has to be a better way
“Never let a man do what a machine can do for him”
Blaise Pascal
Apache Cocoon
A fast-paced overview
Apache Cocoon: facts and figures
Origins Started by the Italian student Stefano Mazzocchi
Redesign of Apache.org Frustrated by the limitations of HTML Wanted to use emerging technologies (XML/XSL)
Today Now one of the most important Apache projects Incorporates technologies from various project Just under 2000 registered on all mailing lists Includes major companies such as HP, IBM
Tucking web devs in, kissing them goodnight
10.000ft overview
URI space decoupling Componentized resource assembling Datasource connections and decoupling Stateful applications
Key Cocoon concepts
URI space decoupling Sitemap
Componentized resource assembling Pipelines
Datasource connections and decoupling Source interface and implementations (file, URL,
database, WebDAV, XMLDB...) Stateful applications
Web continuations
And their implementation...
Think <servlet-mapping> on steroids Pattern freaks think FrontController Uses pluggable algorithms:
Wildcard Regexp Your own
Matching the whole HTTP environment: Request URI/parameters/attributes Session attributes Cookies, context, etc...
Welcome to the sitemap
URI space decoupling: the sitemap
<map:match pattern="news/*" type="wildcard"> <map:generate src="newsfeeds/{1}.xml" type="file"/> <map:transform src="news2html.xsl" type="xslt"/> <map:serialize type="html"/> </map:match> <map:match pattern="products/*" type="wildcard"> <map:generate src="products/infos/product_{1}.xml"
type="file"/> <map:transform src="products2html.xsl" type="xslt"/> <map:serialize type="html"/> </map:match>
A sitemap example
Task: find the total number of unique hosts visiting the home page of your site Windows solution:
#include <stdlib.h>...
Unix solution:$ grep index.html access.log | awk '{print $2 }' | sort |
uniq | wc -l
Welcome to the pipeline: specialized components glued together
Resource assembling: the pipeline
Two simple rules: Data stream is based on SAX events Pipelines start with one Generator (and only one), have 0-
n Transformers, end with one (and only one) Serializer Generators: adapters from the outside world to SAX
events Transformers: SAX event manglers Serializers: adapters from SAX events to the outside
world
The Cocoon pipeline
<map:match pattern="*.html">
<map:generate src=”xdocs/{1}.xml"/>
<map:transform src=“style/xdocs2html.xsl”/>
<map:serialize/>
</map:match>
A pipeline example
Virtualization layer over stream-based data sources Provides a java.io.File like interface for a number of
data sources Think URLs on steroids
Datasource virtualization
The Source interface
<map:match pattern="news/*" type="wildcard"> <map:generate src="webdav://repo/newsfeeds/{1}.xml" type="file"/> <map:transform src="xmldb://localhost/db/news2html.xsl"
type="xslt"/> <map:serialize type="html"/> </map:match> <map:match pattern="products/*" type="wildcard"> <map:generate src="cocoon://products/infos/product_{1}.xml"/> <map:transform src="resource://products2html.xsl" type="xslt"/> <map:serialize type="html"/> </map:match>
A datasource example
The sitemap decouples URI space from physical resources
The pipeline decouples results from the way they're built
XML decouples content from presentation The source abstraction decouples data sources
from physical locations
Result: full Separation of Concerns
(and you ain’t seen nothing yet...)
Summing it up
The next revolution
Web Continuations
Web applications are easy!
Webapps are finite state machines Upon every click, processing starts from the beginning Again, and again, and again
But wait, this is stateless HTTP
A typical web application clutter
“A continuation is an entire set of closures that make up a point of execution”
“A continuation is an object that, for a given point in your program, contains a snapshot of the stack trace, including all the local variables, and the program counter”
Hmmm... so what?
What are continuations?
var cart;var user;
function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
A flowscript example: where the magic happens
– Contents of a continuation● Stack of function calls● Value of local variables
Most often a lightweight object
Creating a continuation does not halt a thread !!
– A continuation object is associated with a unique identifier available to the view Later used to "resurrect" it
What are continuations, again?
saved continuationsvar cart;var user;
function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
Flowscript example, revisited
var cart;var user;function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
Don't fear the Back button anymore!
Continuations tree
– Continuations give control back to the server We always know "where" the browser is
– Allow sophisticated flow screens No need for state automata
– Increase security and robustness Forbids direct access to form submission URLs
Handling of "back" and "new window"
Summing it up
Managing user input
A word about forms
MVC based form framework Provides:
Strongly-typed data: a date field is a java.util.Date. Automatically.
Validation: declarative. Handled for you. Automatically. Binding: to objects (EJB, POJOs...) or to XML, your choice.
Automatically.
Cocoon Forms: nightmares solved!
Separation of Concerns in forms: Model: data typing, validation rules View: widget presentation
selections could be drop down lists or checkboxes Controller:
flowscript (continuations based) custom actions (à la Struts) – not recommended
Data binding: declarativeJXPath based
Cocoon Forms, a functional view
Powerful widget library Fields, aggregated fields, dates, selectors, trees and
repeaters AJAX compliant infrastructure
Boosts perceived performance Integrated add-ons
HTML editor calendar pop-ups dynamic double selection lists
Cocoon Forms: a rich framework
Configure, don't code Continuations make webapp development a breeze Powerful form frameworks ease managing user input Separation of Concerns brings maintainability
... and they lived happily ever after.
Summing it up: RAD development
Installing Cocoon
Finding your way through compilation and blocks
Downloading Cocoon
http://cocoon.apache.org Subversion for the latest version:
http://svn.apache.org/repos/asf/cocoon/branches/BRANCH_2.1.Xhttp://svn.apache.org/repos/asf/cocoon/trunk … or even WebDAV
Preparing Cocoon
Task list: Edit local.build.properties
Exclude unneeded and heavy tasks (e.g. javadocs / documentation)
Edit local.blocks.propertiesBeware of dependencies
All done, now run:
$ ./build.sh (Unix)
C\> build.bat (Windows)
Testing the install
Embedded Jetty: Run
$ ./cocoon.sh servlet (Unix)
C\> cocoon.bat servlet (Windows)
Point your browser to http://localhost:8888/
Other servlet engines: Run$ ./build.sh war (Unix)
C\> build.bat war (Windows)
Deploy dist/cocoon*.war
Beware the classloader
Cocoon uses its own version of Rhino (Javascript engine)
Might clash with vendor-provided ones (e.g. Websphere/BEA)
Make sure that your appserver is configured to run Cocoon in an isolated classloader (as per servlet spec): ask your vendor to fix his bug if it doesn’t work
If it doesn’t work, use the “paranoid” block and the ParanoidCocoonServlet (but you shouldn’t)
Mind redirects on WebSphere!
Eclipse integration
Run:$ ./build.sh eclipse-project (Unix)
C\> build.bat eclipse-project (Windows)
Inside Eclipse: New Java Project Navigate to $COCOON_HOME
Your project will appear automagically!
Files you want to know
Main sitemap:$COCOON_HOME/build/webapp/sitemap.xmap
Log configuration:$COCOON_HOME/build/webapp/WEB-INF/logkit.xconf
Component configuration:$COCOON_HOME/build/webapp/WEB-INF/cocoon.xconf
Mount table:$COCOON_HOME/mount-table.xml
Management and configuration
Handling Cocoon in production
Configuring Cocoon
Web Application Configuration Cocoon runs as a servlet Configuration controlled by web.xml Find it in the WEB-INF directory
Most important entry Name and location of the actual configuration file
Default: WEB-INF/cocoon.xconf
<!--
This parameter points to the main configuration file for Cocoon. Note that the path is specified in absolute notation but it will be resolved relative to the servlets webapp context path
-->
<init-param>
<param-name>configurations</param-name>
<param-value>/WEB-INF/cocoon.xconf</param-value>
</init-param>
Configuring Cocoon
cocoon.xconf XML format Contains Avalon component configuration But not (!!) the sitemap components
They are in the sitemap cocoon.xconf rarely needs changing
Moving from test to productionReplacing the XML parser
Configuring Cocoon
cocoon.xconf
<?xml version="1.0"?>
<cocoon version="2.0">
<parser class="org.apache.cocoon.components.parser.XercesParser"/>
<hsqldb-server class="org.apache.cocoon.components.hsqldb.ServerImpl"
pool-max="1" pool-min="1">
<parameter name="port" value="9002"/>
<parameter name="silent" value="true"/>
<parameter name="trace" value="false"/>
</hsqldb-server>...
</cocoon>
cocoon.xconf No namespace Each component defined inside <cocoon> Logical names matched to implementations
e.g. parser; hsqldb-server Configuration using <parameter>
Contains name-value pairs pool-min and pool-max
We will look at those later (pooling)
Configuring Cocoon
Configuring Cocoon
cocoon.xconf Another important piece of information
Location and name of the sitemap
<sitemap file="context://sitemap.xmap"
check-reload="yes"/>
ReloadingTriggers on incoming requestSynchronous
• New sitemap will be generated and then handles the request
What do you do if an error occurs ............
Configuring Cocoon
LogKit Configuration Each component in Cocoon logs using LogKit There are five log levels
DEBUGINFOWARNINGERRORFATAL_ERROR
In testing: DEBUG In production: ERROR or FATAL_ERROR Location of the LogKit configuration file
Is in the web.xml:<init-param> <param-name>logkit-config</param-name> <param-value>/WEB-INF/logkit.xconf</param-value></init-param>
Configuring Cocoon
LogKit Configuration Consists of several parts
Factories for logging targets• Implementations are not "hard-wired"• Defines components that are to receive the log
messagesTargets
• Configured with Factory• And file name, output format, size/rotation information
Categories• Actual "receiver" of the log messages• Configured with (multiple) targets• And log level
Configuring Cocoon
LogKit Configuration<logkit> <factories> <factory type="cocoon" class="org.apache.cocoon.util.log.CocoonTargetFactory"/> </factories> <targets> <cocoon id="cocoon"> <filename>${context-root}/WEB-INF/logs/cocoon.log</filename> <format type="cocoon"> %7.7{priority} %{time} [%8.8{category}] (%{uri}) %{thread}/%{class:short}: %{message}\n%{throwable} </format> <append>true</append> <rotation type="revolving" init="1" max="4"> <or> <size>100m</size> <time>01:00:00</time> </or> </rotation> </cocoon> <filter/> </targets> <categories> <category name="cocoon" log-level="DEBUG"> <log-target id-ref="cocoon"/> <log-target id-ref="filter"/> </category> </categories></logkit>
Configuring Cocoon
LogKit Configuration (Normally) No need to change the setting
Apart from log-levelsNot reflected automaticallyTouch cocoon.xconf
DEBUG logfiles can become very largeHard drives have a limited capacity And slow performance down
Default level: ERROR for all categories
Dissecting Cocoon
A sitemap tour
Cocoon anatomy
Cocoon physiology
The sitemap disemboweled
The sitemap contains: Component definitions and configurations Views Resources Action sets Flow Pipelines
… all neatly packaged as a namespaced XML file
<map:sitemap xmlns:map="http://xml.apache.org/cocoon/sitemap/1.0"> <map:components/> <map:views/> <map:resources/> <map:action-sets/> <map:flow/> <map:pipelines/></map:sitemap>
Sitemap components
Configured as children of the <map:components> section Contains:
Generators Transformers Serializers Readers Matchers Selectors Actions Pipes …and their configuration
Each section declaration can have a default component
Sitemap components Tour
Generator, Transformers and Serializers are typical Cocoon components for pipelines
Readers are used for binary resources Think of a “collapsed” Generator and Serializer
Matchers are used to route requests Selectors implement branching (if… then… else) logic Actions implement business logic based switching (old
fashioned, flow is now the preferred way) Pipes define different pipeline implementation (which
differ mainly for caching policies)
Examples will follow
A word about Actions
Actions are reusable snippets of business logic Actions wrap pipeline snippets Actions can return either:
null: the pipeline snippet is skipped A Map containing business values, which can be reused in
the pipeline snippet
<map:act type="clear-cache"><map:generate src="status" type="status"/><map:transform src="context://stylesheets/system/status2html.xslt"> <map:parameter name="contextPath" value="{request:contextPath}"/></map:transform><map:serialize/>
</map:act>
Sitemap components example
<map:components> <map:generators/> <map:transformers default="xslt"> <map:transformer logger="sitemap.transformer.xslt" name="xslt" pool-max="32" src="org.apache.cocoon.transformation.TraxTransformer"> <use-request-parameters>false</use-request-parameters> <use-session-parameters>false</use-session-parameters> <use-cookie-parameters>false</use-cookie-parameters> <xslt-processor-role>xalan</xslt-processor-role> <check-includes>true</check-includes>
</map:transformer> </map:transformers> <map:serializers/> <map:readers/> <map:actions/> <map:pipes/></map:components>
Tip: reusing components
Components can be re-defined with different configurations, Names have to be unique
<map:transformer logger="sitemap.transformer.xalan" name="xalan" pool-max="32" src="org.apache.cocoon.transformation.TraxTransformer"> <use-request-parameters>false</use-request-parameters> <use-session-parameters>false</use-session-parameters> <use-cookie-parameters>false</use-cookie-parameters> <xslt-processor-role>xalan</xslt-processor-role> <check-includes>true</check-includes> </map:transformer>
<map:transformer logger="sitemap.transformer.xsltc" name="xsltc" pool-max="32" src="org.apache.cocoon.transformation.TraxTransformer"> <use-request-parameters>false</use-request-parameters> <use-session-parameters>false</use-session-parameters> <use-cookie-parameters>false</use-cookie-parameters> <xslt-processor-role>xsltc</xslt-processor-role> <check-includes>true</check-includes> </map:transformer>
Understanding views
Views are “exit points” in pipeline processing Original motivation: semantic search Current use: mainly debug Configuration:
name: unique identifier from-label: explicit exit point from-position: implicit (automatic) exit point
first: right after the generatorlast: right before the serializer
Activated by cocoon-view=view-name
Warning! Views are not inherited by subsitemaps Warning! Views can be a security concern
More on views: placing labels
Labels are set using the label attribute on either: A Generator or Transformer declaration in the
components section (will work anywhere) A Generate or Transform directive in a pipeline (specific
to the pipeline) An Aggregate or Part directive (more on this later)
View examples
Defining views: <map:views>
<map:view from-label="content" name="content"> <map:serialize type="xml"/> </map:view>
<map:view from-label="content" name="pretty-content"> <map:transform src="stylesheets/system/xml2html.xslt"/> <map:serialize type="html"/> </map:view> </map:views>
Defining labels: <map:generator label="content" logger="sitemap.generator.file"
name="file" pool-max="32”
src="org.apache.cocoon.generation.FileGenerator"/>
Resources
Reusable pipeline fragments Useful for repetitive tasks Will be (mostly) replaced by Virtual Sitemap
Components
Warning! Resources are not inherited by subsitemaps
A resource example
Defining a resource: <map:resources>
<map:resource name="simple-page2html"> <map:transform src="context://samples/common/style/xsl/html/simple-page2html.xsl"> <map:parameter name="contextPath" value="{request:contextPath}"/> <map:parameter name="servletPath" value="{request:servletPath}"/> <map:parameter name="sitemapURI" value="{request:sitemapURI}"/> <map:parameter name="file" value="{file}"/> <map:parameter name="remove" value="{../0}"/> </map:transform> </map:resource> </map:resources>
Calling a resource<map:call resource="simple-page2html">
<map:parameter name="file" value="forms/form1_success.xsp"/></map:call>
Action-sets
Define a set of actions to be executed as a unit of work either: Unconditionally (upon every invocation) When explicitely called, using a cocoon-action
parameter
<map:action-sets> <map:action-set name="shop-actions"> <map:act type="session-invalidator" action="logoff"/> <map:act type="session-validator"/> <!-- Always executed --> <map:act type="cart-add" action="addItem"/> <map:act type="cart-remove" action="removeItem"/> <map:act type="cart-remove-all" action="removeAll"/> <map:act type="cart-update" action="updateQty"/> <map:act type="order-add" action="addOrder"/> <map:act type="order-verify" action="verifyOrder"/> <map:act type="navigator" src="{1}"/> <!-- Always executed -->
</map:action-set></map:action-sets>
Declaring flow scripts
Flow is a new way to insert page flow control in your application (much more on that later)
Scripts and/or classes are declared in the flow section of the sitemap
<map:flow language="javascript">
<map:script src="calc.js"/>
</map:flow>
<map:flow language="java"><map:script
src="org.apache.cocoon.samples.flow.java.CalculatorFlow"/><map:script src="org.apache.cocoon.samples.flow.java.FormFlow"/><map:script
src="org.apache.cocoon.samples.flow.java.PersistenceFlow"/>
</map:flow>
The pipelines section
The “heart” of Cocoon, where things get done Contains 1+ <map:pipeline> declarations Any pipeline section represents a logical division Different pipelines serve different purposes:
Visibility: internal-only pipelines can only be just using the cocoon: protocol;
Caching, as pipelines can be: non-cached (always executed) cached delegating validity to the pipeline components
(executed if nothing has changed in the pipeline) cached beforehand using the expires directive (executed
only once during the validity period)
Cocoon pipelines dissected
Pipelines are “recipes” building resources SAX events travel through the pipeline The TLA is GTS: Generator, Transformer, Serializer Generators are adapters from the outside world to SAX
events Transformers are SAX filters Serializers are the opposite of Generators, adapting
SAX events to the outside world The obligatory diagram:
What’s in a pipeline?
Well, components in action: from nouns to verbs
Content production: generate [- transform ] - serialize aggregate - part [ - part …] read
Branching: match select act call
So what’s the sitemap, again?
Central switchboard (or FrontController, if you like patterns)
Contains component declarations (yes, even business components)
Locates (match) and builds (pipelines) the final result In most cases, it’s the only file you’ll need to touch
A typical request cycle in Cocoon
1. The environment is checked for the proper pipeline to use (normally via matching/selecting)
2. The pipeline is evaluated:1. Sitemap components are looked up via type references
or default assignment2. The pipeline is setup.
3. The pipeline is executed
Warning: no dynamic sitemap routing!
Zen and the art of matching
A perfectly valid pipeline example
<map:pipeline>
<map:generate src=”hello.xml" type="file"/>
<map:transform src=”hello2html.xsl" type="xslt"/>
<map:serialize type="html"/>
</map:pipeline>
Would match any request and greet the user … which is probably not what you want
Matchers kick in
Matchers associate the actual environment to a pipeline<map:pipeline>
<map:match pattern=”hello” type="wildcard">
<map:generate src=”hello.xml" type="file"/>
<map:transform src=”hello2html.xsl" type="xslt"/>
<map:serialize type="html"/>
</map:match>
<map:pipeline>
Hit http://localhost:8888/hello
Semantic problem
Warning, pitfall ahead! Formally a pipeline is everything included in
<map:pipeline> Colloquially, though, a pipeline is normally the G-T-S part
<map:pipeline> <map:match pattern=”hello” type="wildcard"> <map:generate src=”hello.xml" type="file"/> <map:transform src=”hello2html.xsl" type="xslt"/> <map:serialize type="html"/> </map:match> <map:match pattern=”goodbye” type="wildcard"> <map:generate src=”goodbye.xml" type="file"/> <map:transform src=”bye2html.xsl" type="xslt"/> <map:serialize type="html"/> </map:match><map:pipeline>
Why the fuss?
A pipeline, formally, is everything that starts with a Generator and ends with a Serializer
This sample, then, is perfectly valid:
1:<map:pipeline> 2: <map:match pattern=”hello” type="wildcard"> 3: <map:generate src=”hello.xml" type="file"/> 4: </map:match> 5: <map:match pattern=”goodbye” type="wildcard"> 5: <map:generate src=”goodbye.xml" type="file"/> 7: </map:match> 8: <map:transform src=”greeting2html.xsl" type="xslt"/> 9: <map:serialize type="html"/>10:<map:pipeline>
To make things further complicated…
This one is valid as well: 1:<map:pipeline>
2: <map:match pattern=”hello” type="wildcard">
3: <map:generate src=”hello.xml" type="file"/>
4; <map:transform src=”greeting2html.xsl" type="xslt"/>
5: <map:serialize type="html"/>
6: </map:match>
7: <map:match pattern=”goodbye” type="wildcard">
8: <map:generate src=”goodbye.xml" type="file"/>
9: </map:match>
10: <map:transform src=”greeting2html.xsl" type="xslt"/>
11: <map:serialize type="html"/>
12:<map:pipeline>
Argh! Where is my aspirin?
… but this one could not be:
1:<map:pipeline>
2: <map:generate src=”hello.xml" type="file"/>
3: <map:match pattern=”hello” type="wildcard">
4: <map:generate src=”hello.xml" type="file"/>
5: <map:transform src=”greeting2html.xsl" type="xslt"/>
6: <map:serialize type="html"/>
7: </map:match>
8: <map:match pattern=”goodbye” type="wildcard">
9: <map:generate src=”goodbye.xml" type="file"/>
10: </map:match>
11: <map:transform src=”greeting2html.xsl" type="xslt"/>
12: <map:serialize type="html"/>
13:<map:pipeline>
Bottom line
Cocoon will process the sitemap and try to build a G-[T]-S pipeline
Cocoon will stop processing when a Serializer is met Beware the “Generator already set” errors! You can’t have a “Serializer already set” error
Understanding matchers
Matchers will “route” a user request through Cocoon pipelines
Matchers are evaluated in order: first match wins Matchers are available for the whole environment,
matching on: Request URI Request parameters Request Attributes Session Attributes Cookies … and more
Matchers are pluggable: write your own!
Using matchers
A basic example:
<map:match pattern=""> <map:generate src="welcome.xml"/> <map:transform src="welcome.xslt"/> <map:serialize type="xhtml"/> </map:match>
Captures an empty URI
Wildcard-based matching
Wildcard match part of the URI using special chars (*/**) A single asterisk matches everything up to the first forward
slash <map:match pattern=”welcome-*">
<map:generate src="welcome.xml"/> <map:transform src="welcome.xslt"/> <map:serialize type="xhtml"/> </map:match>
Matches http://localhost:8888/welcome-friend A double asterisk matches everything <map:match pattern=”welcome**">
<map:generate src="welcome.xml"/> <map:transform src="welcome.xslt"/> <map:serialize type="xhtml"/> </map:match>
Matches http://localhost:8888/welcome/friend
Capturing matcher results
Captured parts of a URI are available for later use Ordered list, denoted by position number in curly
braces Starts at 1 <map:match pattern=”welcome-*">
<map:generate src="welcome-files/{1}.xml"/> <map:transform src="welcome.xslt"/> <map:serialize type="xhtml"/> </map:match>
Matches http://localhost:8888/welcome-friend Uses welcome-files/friend.xml as the generator input
Can be arbitrarily complex: <map:match pattern="linkstatus/*/*/**">
<map:generate src="http://{1}:{2}/{3}" type="linkstatus"/> <map:transform src="stylesheets/system/linkstatus2html.xslt"/> <map:serialize/> </map:match>
Nesting matchers
Matchers can be nested: <map:match pattern=”welcome-**">
<map:match pattern=”friend-*">
<map:generate src="welcome-files/ {1}.xml"/>
</map:match>
<map:match pattern=”foe-*">
<map:generate src=”goaway/{1}.xml"/>
</map:match> <map:transform src="welcome.xslt"/> <map:serialize type="xhtml"/> </map:match>
Nesting matchers: capturing parent results
The matched expression on the child matcher doesn’t take into account the part already matched
“Parent” captured expressions are available with a tree navigation syntax:
<map:match pattern=”welcome-**"> <map:match type=”host-matcher” pattern=”localhost"> <map:generate src=”local-files/{../1}.xml"/> </map:match> <map:match type=”host-matcher” pattern=”*.mydomain.com"> <map:generate src=”remote-files/{1}/{../1}.xml"/> </map:match>
<map:transform src="welcome.xslt"/> <map:serialize type="xhtml"/> </map:match>
Dealing with Sources
The Source abstraction
Abstracts a stream based data source Sub interfaces define write mode and tree-like
navigation (à la java.io.File) URL-like syntax (with some bastardization)
The Source interface
Available sources
Remote sources: URLSource: deals with every protocol available for java.net.URL FileSource: manages local files WebDAVSource: interoperates with WebDAV repositories XMLDBSource: connects to NXDs BlobSource: uses databases blobs
“Meta” sources: SitemapSource: uses Cocoon pipelines as data stream sources ResourceSource: grabs data from classpath ContextSource: accessed streams from the webapp context ModuleSource: converts modules into sources CachedSource: decorates sources adding passive caching
The Sitemap Source
Calls cocoon pipelines Weirdo:
When used in a generator or transformer, the called pipeline serializer will be ignored
When used in a reader, the serializer will be honored More weirdos: URL bastardization
cocoon:/ will call a pipeline starting from the current sitemap
cocoon:// will call a pipeline starting from the root sitemap
Cocoon Components Tour
A journey through Generators, Transformers, Serializers, Readers,
Matchers, Sub-sitemaps, Modules, Error handling and more
Components Tour
Cocoon has roughly 400 components: 66 Generators 65 Transformers 23 Serializers 10 Readers 37 Matchers 31 Selectors 88 Actions 59 Input/Output Modules
And you might want to write your own No way to cover them all… we’ll see the most useful
Useful stuff: FileGenerator
Actually it’s a SourceGenerator: can deal with every Source data stream
… which includes cocoon:// URIs Probably the most used generator in Cocoon
Useful stuff: Directory Traversal
Operate on TraversableSources (directories) Provide an XML listing of the requested resources Available components:
DirectoryGeneratorImageDirectoryGeneratorMP3DirectoryGenerator
TraversableGenerator TraversableSourceDescriptionGenerator XPathTraversableGenerator
Useful stuff: more generators
JXTemplateGenerator: inspired by JSTL, more on this later
RequestGenerator: XMLized Request object
SessionAttributeGenerator: streams an object stored in session as XML
StreamGenerator: reads and streams XML from a request InputStream (e.g. POST requests)
HttpProxyGenerator: accesses an XML stream over HTTP
HTMLGenerator: grabs HTML from a source, converts to XHTML using jTidy and streams it away
Useful stuff: TraxTransformer
Can use either (and concurrently): TRAX Xalan Saxon
Relevant configuration parameters:
<use-request-parameters>true|false</use-request-parameters><use-session-parameters>true|false</use-session-parameters><use-cookie-parameters>true|false</use-cookie-parameters><xslt-processor-role>trax|xalan|xsltc</xslt-processor-role><check-includes>true</check-includes>
Beware caching impact!
Useful stuff: I18NTransformer
Incredibly useful for dictionaries and localization Golden rule: use it Relevant configuration:
<map:transformer name="i18n" logger="sitemap.transformer.i18n" src="org.apache.cocoon.transformation.I18nTransformer"><catalogues default="messages"> <catalogue id="messages" name="messages" location="translations"/> <catalogue id="menu" name="menu" location="translations"/> <catalogue id="tiered" name="messages"> <location>translations/tiered</location> <location>translations</location> </catalogue></catalogues><cache-at-startup>true</cache-at-startup>
</map:transformer>
Dissecting I18NTransformer
Resolving catalogue files: name, id: unique identifiers and base names for files location: a source to be prepended to the base names Locale will be appended, in full or short form (en_US or en
as a fallback). Finally, “.xml” will complete the file name
So: <catalogue id="messages" name="messages” location="translations"/>
Assuming “en_US” as locale, the transformer will look for:
translations/messages_en_US.xml
translations/messages_en.xml
translations/messages.xml
I18N catalog files
<catalogue xml:lang="en">
<message key="Apache Cocoon i18n Samples">Apache Cocoon i18n Samples</message><message key="Samples">Samples</message><message key="Introduction">Introduction</message><message key="Static (XML)">Static (XML)</message><message key="Dynamic (XSP)">Dynamic (XSP)</message><message key="Sitemap source">Sitemap source</message><message key="Locales">Locales</message><message key="Documentation">Documentation</message><message key="i18n transformer docs"><![CDATA[<I18ntransformer> docs]]></message><message key="i18n transformer Javadoc"><![CDATA[<I18ntransformer> Javadoc]]></message><message key="LocaleAction Javadoc"><![CDATA[<LocaleAction> Javadoc]]></message><message key="Credits">Credits</message><message key="Konstantin Piroumian">Konstantin Piroumian</message><message key="Many others...">Many others...</message>
</catalogue>
Using the I18NTransformer
From then you can happily translate:
Elements<title>
<i18n:text>titletext</i18n:text></title>
Attributes<para title="first" name="article" i18n:attr="title name">
Dates<i18n:date-time pattern="MEDIUM"/>
Numbers<i18n:number type="percent" src-locale="en" value="1.2"/>
Useful stuff: CIncludeTransformer
Dynamically inserts content in XML streams Reacts to elements in the http://apache.org/cocoon/include/1.0
namespace <page
xmlns:cinclude="http://apache.org/cocoon/include/1.0"> <title>Hello</title> <content> <para>This is my first Cocoon page!</para> <cinclude:include src="include.xml" element="included"/> </content></page>
Can be cached as well: <map:transform type="cinclude">
<map:parameter name="expires" value="600"/> </map:transform>
<cinclude:cached-include src="include.xml"/>
Useful stuff: SourceWritingTransformer
Useful to write XML data to (Writeable) Sources during pipeline execution e.g., save a file
Reacts to elements in the http://apache.org/cocoon/source/1.0 namespace
Typically used with XSLT building the source:* stuff Read the Javadocs for details <page>
<source:write create="true”
xmlns:source="http://apache.org/cocoon/source/1.0"> <source:source>file://tmp/test.write</source:source> <source:fragment> <title>a title</title> <content> ... </content> </source:fragment> </source:write></page>
Useful stuff: XMLSerializer
Outputs, well, XML. Configurable encodings and doctypes:
<map:serializer logger="sitemap.serializer.xhtml"
mime-type="application/xhtml+xml" name="xhtml11”
src="org.apache.cocoon.serialization.XMLSerializer"><doctype-public>-//W3C//DTD XHTML 1.1//EN</doctype-public><doctype-system>
http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd
</doctype-system>
<encoding>UTF-8</encoding>
</map:serializer>
Useful stuff: FOPSerializer
Uses Apache FOP to produce: PDF PS PCL RTF
<map:serializer logger="sitemap.serializer.fo2pdf" mime-type="application/pdf" name="fo2pdf"
src="org.apache.cocoon.serialization.FOPSerializer"> <user-config>WEB-INF/fop-config.xml</user-config> <set-content-length>true</set-content-length>
</map:serializer>
<map:serializer logger="sitemap.serializer.fo2ps" mime-type="application/postscript" name="fo2ps"
src="org.apache.cocoon.serialization.FOPSerializer"/>
<map:serializer logger="sitemap.serializer.fo2pcl" mime-type="vnd.hp-PCL" name="fo2pcl"
src="org.apache.cocoon.serialization.FOPSerializer"/>
Useful stufff: SVGSerializer
Takes SVG as input, produces JPEG/PNG as output Uses Apache Batik
<map:serializer logger="sitemap.serializer.svg2jpeg"
mime-type="image/jpeg" name="svg2jpeg" src="org.apache.cocoon.serialization.SVGSerializer"> <parameter name="quality" type="float" value="0.9"/>
</map:serializer>
<map:serializer logger="sitemap.serializer.svg2png"
mime-type="image/png" name="svg2png”
src="org.apache.cocoon.serialization.SVGSerializer"/>
Useful stuff: ImageReader
Will serve images (surprise!) The kick: performing width, height, ratio adjustments
(great for thumbnails)
<map:match pattern="image-size-*">
<map:read type="image" src="logo.jpg"> <map:parameter name="width" value="{1}"/> <map:parameter name="height" value="{1}"/> </map:read>
</map:match>
Sub sitemaps
Sitemaps could become overpopulated and messy (the Perl effect)
Solution: modularize using sub-sitemaps From the main sitemap match the parent path and
mount a sitemap underneath: <map:match pattern="xsp/**">
<map:mount uri-prefix="xsp" src="xsp.xmap" /> </map:match>
Wildcard substitution, of course, works: <map:match pattern="~*/**">
<map:mount src="/home/{1}/public_html/" uri-prefix="~{1}"/>
</map:match>
Input/Output Modules
Modules create generic components and plug actual input/output at runtime
Example: grab a request parameter and use it in the sitemap
<map:match pattern=”welcome">
<map:generate src=”local-files/{request-param:file}.xml"/>
<map:transform src="welcome.xslt"/> <map:serialize type="xhtml"/> </map:match>
Again: no dynamic routing possible Modules are evaluated during the pipeline setup phase
Useful modules: GlobalInputModule
Allows to configure global variables in the sitemap, with runtime substitution
Helps writing maintainable code <map:component-configurations>
<global-variables> <staging>webdav://localhost/webdav/step1/</staging> </global-variables> </map:component-configurations>
<map:pipeline>
<map:match pattern="repo/"> <map:generate type="traverse" src="{global:staging}repo/"> <map:parameter name="depth" value="1"/> </map:generate> <map:serialize type="xml"/> </map:match>
</map:pipeline>
Useful modules: XMLFileInputModule
Allows XPath queries on external resources Helps maintainability: configuration will be outside the
sitemap Configured in cocoon.xconf
<component-instance class="org.apache.cocoon.components.modules.input.XMLFileModule" logger="core.modules.xml" name=”settings"> <file src="cocoon://${project.mount}/configuration/test.xhtml" reloadable="false" cacheable="true"/></component-instance>
… <map:generate type="traverse" src="{settings:/html/head/title}">
When things go wrong: error handling
Error conditions (exception) can be catched in the pipeline
Each pipeline can have a handle-errors section Error handling is inherited NotifyingGenerator provides errors in XML format ExceptionSelector allows branching according to the
exception
Error handling explained<map:selector logger="sitemap.selector.exception" name="exception" src="org.apache.cocoon.selection.ExceptionSelector"> <exception class="o.a.c.ResourceNotFoundException" name="not-found"/> <exception class="o.a.c.c.flow.InvalidContinuationException" name="invalid-continuation"/></map:selector>
<map:handle-errors> <map:select type="exception">
<map:when test="not-found"> <map:generate type="notifying"/> <map:transform src="stylesheets/system/error2html.xslt”/> <map:serialize status-code="404"/> </map:when>
<map:when test="invalid-continuation"> <map:generate type="notifying"/> <map:transform src="stylesheets/system/error2html.xslt"> <map:serialize status-code="404"/> </map:when>
<map:otherwise> <map:generate type="notifying"/> <map:transform src="stylesheets/system/error2html.xslt"> <map:serialize status-code="500"/> </map:otherwise></map:select>
</map:handle-errors>
Flowscript
The magic of web continuations
Introduction JavaScript features View layer Putting it all together Session variables Managing continuations
Overview
Need for flow control What is "flow control" ?
Control of transition between pagesDriven by application logic
Aren't actions enough ?Yes, but they require state managementQuickly becomes complex, hard to understand and to
maintain
Introduction
Flow script : what is it ? Simple and effective way to glue together business
logic, presentation and page flow Uses scripts written in JavaScript (server-side)
a simple scripting languagecan implement the most complex use cases
Why JavaScript ?Simpler than Java, although powerfulIntegrates well with JavaWell-known in the web worldAllows faster roundtrips (save and reload)Supports continuations
Introduction
Flow script example
var cart;var user;
function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
var cart;var user;
function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
Introduction
JavaScript vs. Java detour If you know Java, you already know JavaScript !
Well, mostly. JavaScript is dynamically typed (variables don’t have
types, values do) Prototype-based inheritance as opposed to class-
based inheritance Objects are extensible at runtime : add or remove
properties and methods
Introduction
cocoon.sendPage() cocoon.sendPage invokes the output page (view)
with two argumentsThe view URL, relative to current sitemapA context Map made available to the view
Can contain Java or JavaScript objects
cocoon.sendPage("view.html") is like redirecting to "cocoon:/view.html"
Control then comes back to the script Should normally terminate
cocoon.sendPage("checkout.html", {user: loggedUser, email: address});
cocoon.sendPage("checkout.html", {user: loggedUser, email: address});
Calling the view
cocoon.sendPageAndWait() Similar to cocoon.sendPage
Invoke the view with a context object The script is suspended after the view is generated
the whole execution stack saved in a continuation object
Flow between pages becomes sequential code No more complicated state automata
Calling the view
What is it ? Contents of a continuation
Stack of function callsValue of local variables Most often a lightweight object
Creating a continuation does not halt a thread !! A continuation object is associated with a unique
identifier available to the view Later used to "resurrect" it
Continuations
Sample flow script revisited
saved continuations
var cart;var user;
function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
var cart;var user;
function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
Continuations
What is JXTemplate ? An XML template language inspired by JSTL Doesn't allow code, but only access to context
variables More simple, more secure
Flow values are provided as variables :
2 expression languages : Jexl and JXPath
<jx:forEach var="item" items="${cart.items}"> <p>Name: <jx:out value="${item.name}"/></p></jx:forEach>
<jx:forEach var="item" items="${cart.items}"> <p>Name: <jx:out value="${item.name}"/></p></jx:forEach>
<a href="kont/${continuation.id}">Continue</a><a href="kont/${continuation.id}">Continue</a>
View layer : JXTemplate
Jexl JSTL & Velocity's expression language
JavaBean property navigation language Property navigation using "."
Expressions enclosed in ${…}
More suited to Java objects
You are calling from ${request.remoteHost}You are calling from ${request.remoteHost}
http://jakarta.apache.org/commons/jexl/
View layer : JXTemplate
JXPath XPath on abitrary object graphs
XML documents, but also JavaBeansExpressions enclosed in #{…}
Equally suited to XML documents and Java objects
You are calling from #{$request/remoteHost}You are calling from #{$request/remoteHost}
http://jakarta.apache.org/commons/jxpath/
View layer : JXTemplate
The sitemap<map:flow language="JavaScript"> <map:script src="store.js"/></map:flow>
<map:pipelines> <map:pipeline> <map:match pattern="checkout/"> <map:call function="checkout"/> </map:match>
<map:match pattern="*.html"/> <map:generate src="{1}.jx"/> <map:transform src="page2html.xsl"/> <map:serialize/> </map:match>
<map:match pattern="kont/*"> <map:call continuation="{1}"/> </map:match>…/…
<map:flow language="JavaScript"> <map:script src="store.js"/></map:flow>
<map:pipelines> <map:pipeline> <map:match pattern="checkout/"> <map:call function="checkout"/> </map:match>
<map:match pattern="*.html"/> <map:generate src="{1}.jx"/> <map:transform src="page2html.xsl"/> <map:serialize/> </map:match>
<map:match pattern="kont/*"> <map:call continuation="{1}"/> </map:match>…/…
Ressurect a continuation
Ressurect a continuation
Called by the flow
Called by the flow
Call a flow function
Call a flow function
Putting it all together
Recap Controller is composed of flow scripts written in
JavaScriptuse sendPageAndWait() to send a response and
temporarily suspend execution Views are regular pipeline with access to flow data
JPath XSP tag libraryJXTemplate generator
Model : your Java business logic Sitemap glues everything together
Putting it all together
Access to the environment "request", "response", "session " and "context"
properties "parameter" : sitemap parameters
Access to the framework Logging :"log" property
cocoon.log.debug("Hi there"); Getting components
cocoon.getComponent("org.apache.excalibur.xml.Parser")
cocoon.releaseComponent(parser)
The "cocoon" global object
Script modularization JavaScript has no "import" feature
cocoon.load("resource://other/script.js")
Page flow control cocoon.sendPage(), cocoon.sendPageAndWait()
Internal calls to the sitemap cocoon.redirectTo("foo.html")
External redirect sent to the browser
The "cocoon" global object
Global scope = session scope Global variables are attached to the session
Saved across top-level function invocationsSpecific to each user
Removes most of the needs for session attributes !
Session variables
Example
var user = null;
function login() { while (user == null) { sendPageAndWait("login.html"); user = UserRegistry.getUser( cocoon.request.getParameter("name"), cocoon.request.getParameter("password") ); }}
function placeOrder() { login(); Accounting.placeOrder(user); sendPage("orderPlaced.html");}
function logout() { user = null; sendPage("bye.html");}
var user = null;
function login() { while (user == null) { sendPageAndWait("login.html"); user = UserRegistry.getUser( cocoon.request.getParameter("name"), cocoon.request.getParameter("password") ); }}
function placeOrder() { login(); Accounting.placeOrder(user); sendPage("orderPlaced.html");}
function logout() { user = null; sendPage("bye.html");}
Shows the login screen
only if needed
Shows the login screen
only if needed
Won't pass through if not
logged !
Won't pass through if not
logged !
Just clear user info to log out
Just clear user info to log out
Session variables
var cart;var user;function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
var cart;var user;function checkout(){ while(user == null) { cocoon.sendPageAndWait("login.html");
user = UserRegistry.getUser(cocoon.request.get("name")); } cocoon.sendPageAndWait("shippingAddress.html", {who: user});
var address = cocoon.request.get("address"); cocoon.sendPageAndWait("creditCard.html");
var creditCard = cocoon.request.get("creditCard"); cocoon.sendPageAndWait("confirmOrder.html");
EnterpriseSystem.placeOrder(user, cart, address, creditCard); cocoon.sendPage("orderPlaced.html");}
Continuation trees Browser "back" or "new window"
…
Managing continuations
Continuation trees Browser "back" : the previous path is lost No fear : a continuation is lightweight
Reference to the parent continuation Local variables since the parent continuation
Browser "new window"Creates a new branch Allows "what if ?" navigation in the application
Managing continuations
Expiring continuations Manual expiration :
sendPageAndWait() returns its continuation k k.invalidate() invalidates the continuation and its
subtree
Again, avoids complicated state management Automatic expiration
An inactive continuation expires after a delay
var k = sendPageAndWait("start.html");...BusinessService.commit();// Cannot go back againk.invalidate();
var k = sendPageAndWait("start.html");...BusinessService.commit();// Cannot go back againk.invalidate();
Managing continuations
Flow script Gives control back to the server
We always know "where" the browser is Allows sophisticated flow screens
No need for state automata Increases security and robustness
Forbids direct access to form submission URLs Handling of "back" and "new window"
Conclusion
Flow script debugger Activated in cocoon.xconf
Development tools
Dealing with forms
The powerful Cocoon forms framework
Introduction
The need for form handling Cocoon started as a publication framework
Many pages, limited user feedback Content was mostly written "outside"
Evolution towards a general-purpose web framework Published content has to be managed Used for more and more data-centric applications
Need for good form handling featuresVarious attempts before Cocoon Forms…
Cocoon Forms principles
Main requirements Strong typing and formatting
A date input will give a java.util.Date Support for localized input formats
No requirement for a form bean The form has its own data model
Easy and safe binding to the data model Only when the all inputs are valid No direct link from request to application data
Strong separation of form definition and styling Extensibility
Everything is a component
Cocoon Forms principles
The big picture
Cocoon Forms principles
The form object model Composed of "widgets"
Represents "something" that appears in the formCan read, parse and validate itselfCan output its XML representation
public interface Widget { public Widget getParent(); public void setParent(Widget widget); public Widget getWidget(String id); public String getId(); public String getFullyQualifiedId(); public void readFromRequest(FormContext formContext); public Object getValue(); public void setValue(Object object); public boolean validate(FormContext formContext);
public void generateSaxFragment(ContentHandler contentHandler, Locale locale); public void generateLabel(ContentHandler contentHandler);}
public interface Widget { public Widget getParent(); public void setParent(Widget widget); public Widget getWidget(String id); public String getId(); public String getFullyQualifiedId(); public void readFromRequest(FormContext formContext); public Object getValue(); public void setValue(Object object); public boolean validate(FormContext formContext);
public void generateSaxFragment(ContentHandler contentHandler, Locale locale); public void generateLabel(ContentHandler contentHandler);}
Widget hierarchy
Naming
Parsing and
validation
XML output
Cocoon Forms principles
Form definition overview
<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"> <fd:widgets> <fd:field id="name" required="true"> <fd:label>Name:</fd:label> <fd:datatype base="string"> <fd:validation> <fd:length min="2"/> </fd:validation> </fd:datatype> </fd:field>
<fd:field id="email" required="true"> <fd:label>Email address:</fd:label> <fd:datatype base="string"> <fd:validation> <fd:email/> </fd:validation> </fd:datatype> </fd:field> …/… <fd:widgets></fd:form>
<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"> <fd:widgets> <fd:field id="name" required="true"> <fd:label>Name:</fd:label> <fd:datatype base="string"> <fd:validation> <fd:length min="2"/> </fd:validation> </fd:datatype> </fd:field>
<fd:field id="email" required="true"> <fd:label>Email address:</fd:label> <fd:datatype base="string"> <fd:validation> <fd:email/> </fd:validation> </fd:datatype> </fd:field> …/… <fd:widgets></fd:form>
Cocoon Forms principles
Form template overview Embeds widget references in target markup
<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"> <head> <title>Registration form</title> </head> <body> <h1>Registration</h1> <ft:form-template action="registration" method="POST"> <ft:widget-label id="name"/> <ft:widget id="name"/> <br/> <ft:widget-label id="email"/> <ft:widget id="email"/> <br/> …/… <input type="submit"/> </ft:form-template> </body></html>
<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"> <head> <title>Registration form</title> </head> <body> <h1>Registration</h1> <ft:form-template action="registration" method="POST"> <ft:widget-label id="name"/> <ft:widget id="name"/> <br/> <ft:widget-label id="email"/> <ft:widget id="email"/> <br/> …/… <input type="submit"/> </ft:form-template> </body></html>
Cocoon Forms principles
Resulting output
The form definition file
Widgets Standard widgets
<fd:form> : the main form widget<fd:field> : "atomic" input field<fd:booleanfield> : boolean input<fd:mutivaluefield> : multiple selection in a list<fd:repeater> : collection of widgets<fd:output> : unmodifiable widget<fd:action> : action button<fd:tree> : a tree-shaped widget
They're all defined in cocoon.xconf Add your own if needed
The form definition file
The <fd:field> widget Definition overview
The label can contain abitrary markup
Including i18n references
<fd:field id="..." required="true|false"> <fd:label>...</fd:label> <fd:datatype base="..."> [...] </fd:datatype> <fd:selection-list> [...] </fd:selection-list></fd:field>
<fd:field id="..." required="true|false"> <fd:label>...</fd:label> <fd:datatype base="..."> [...] </fd:datatype> <fd:selection-list> [...] </fd:selection-list></fd:field>
<fd:label>Your <b>name</b></fd:label><fd:label>Your <b>name</b></fd:label>
<fd:label> <i18n:text key="name-field-label"/></fd:label>
<fd:label> <i18n:text key="name-field-label"/></fd:label>
The form definition file
Defining the data type of a field Mandatory "base" type
Defines the Java type"string", "long", "decimal", "date", "boolean"
Pluggable components : add your own ! Optional conversion and validation
<fd:datatype base="..."> <fd:convertor> [...] </fd:convertor> <fd:validation> [...] </fd:validation></fd:datatype>
<fd:datatype base="..."> <fd:convertor> [...] </fd:convertor> <fd:validation> [...] </fd:validation></fd:datatype>
Parsing and
formatting
Validation
The form definition file
Data type parsing and formatting Each base type has a set of converters
Pluggable components : add your own ! Example : date's "formatting" converter
based on java.text.SimpleDateFormat locale-dependent patterns
<fd:datatype base="date"> <fd:convertor type="formatting"> <fd:patterns> <fd:pattern>yyyy-MM-dd</fd:pattern> <fd:pattern locale="en">MM/dd/yyyy</fd:pattern> <fd:pattern locale="fr">dd/MM/yyyy</fd:pattern> <fd:pattern locale="nl-BE">dd/MM/yyyy</fd:pattern> <fd:pattern locale="de">dd.MM.yyyy</fd:pattern> </fd:patterns> </fd:convertor></fd:datatype>
<fd:datatype base="date"> <fd:convertor type="formatting"> <fd:patterns> <fd:pattern>yyyy-MM-dd</fd:pattern> <fd:pattern locale="en">MM/dd/yyyy</fd:pattern> <fd:pattern locale="fr">dd/MM/yyyy</fd:pattern> <fd:pattern locale="nl-BE">dd/MM/yyyy</fd:pattern> <fd:pattern locale="de">dd.MM.yyyy</fd:pattern> </fd:patterns> </fd:convertor></fd:datatype>
The form definition file
Data type validation A validation rule checks value validity
length, range, regexp, creditcard, assert, email Pluggable components : add your own !
A datatype can have several validation rules Example : email input field
<fd:field id="email"> <fd:datatype base="string"> <fd:validation> <fd:length max='100'/> <fd:email> <fd:failmessage>Not a valid email address!</fd:failmessage> </fd:email> </fd:validation> </fd:datatype></fd:field>
<fd:field id="email"> <fd:datatype base="string"> <fd:validation> <fd:length max='100'/> <fd:email> <fd:failmessage>Not a valid email address!</fd:failmessage> </fd:email> </fd:validation> </fd:datatype></fd:field>
The form definition file
Selection lists Provide enumerations to the user
List of items having a valueOptional item label
Selection lists can be external and dynamic
<fd:field name="OS"> <fd:datatype base="string"/> <fd:selection-list> <fd:item value="Linux"/> <fd:item value="Windows"/> <fd:item value="Mac OS"/> <fd:item value="Solaris"/> <fd:item value="other"> <fd:label><i18n:text key="other"/></fd:label> </fd:item> </fd:selection-list></fd:field>
<fd:field name="OS"> <fd:datatype base="string"/> <fd:selection-list> <fd:item value="Linux"/> <fd:item value="Windows"/> <fd:item value="Mac OS"/> <fd:item value="Solaris"/> <fd:item value="other"> <fd:label><i18n:text key="other"/></fd:label> </fd:item> </fd:selection-list></fd:field>
<fd:selection-list src="cocoon:/build-list.xml"> <fd:selection-list src="cocoon:/build-list.xml">
The form definition file
The <fd:booleanfield> widget Boolean values
Usually rendered as a checkbox
<fd:booleanfield id="somebool">
<fd:label>Put me <em>on</em> or <em>off</em>.</fd:label>
</fd:booleanfield>
<fd:booleanfield id="somebool">
<fd:label>Put me <em>on</em> or <em>off</em>.</fd:label>
</fd:booleanfield>
The form definition file
The <fd:multivaluefield> widget Allows the selection of several values
The value is an Object[] Requires a <fd:selection-list>
<fd:multivaluefield id="drinks">
<fd:label>Indicate your 2 most preferred drinks:</fd:label> <fd:datatype base="string"> <fd:validation> <fd:value-count exact="2"/> </fd:validation> </fd:datatype>
<fd:selection-list> <fd:item value="Maes"/> <fd:item value="Jupiler"/> <fd:item value="Leffe"/> <fd:item value="Hoegaarden"/> <fd:item value="Coca Cola"/> </fd:selection-list>
</fd:multivaluefield>
<fd:multivaluefield id="drinks">
<fd:label>Indicate your 2 most preferred drinks:</fd:label> <fd:datatype base="string"> <fd:validation> <fd:value-count exact="2"/> </fd:validation> </fd:datatype>
<fd:selection-list> <fd:item value="Maes"/> <fd:item value="Jupiler"/> <fd:item value="Leffe"/> <fd:item value="Hoegaarden"/> <fd:item value="Coca Cola"/> </fd:selection-list>
</fd:multivaluefield>
The form definition file
The <fd:repeater> widget Repeats a number of child widgets
Used to manage collections, tables, etc.
<fd:repeater id="contacts">
<fd:field id="firstname"> <fd:label>Firstname</fd:label> <fd:datatype base="string"/> </fd:field>
<fd:field id="lastname"> <fd:label>Lastname</fd:label> <fd:datatype base="string"/> </fd:field>
</fd:repeater>
<fd:repeater id="contacts">
<fd:field id="firstname"> <fd:label>Firstname</fd:label> <fd:datatype base="string"/> </fd:field>
<fd:field id="lastname"> <fd:label>Lastname</fd:label> <fd:datatype base="string"/> </fd:field>
</fd:repeater>
The form definition file
The <fd:output> widget Read-only widget
Provides formatting featuresBut doesn't read its value from the request
<fd:output id="shipping-date"> <fd:label> <i18n:text key="shipping-date.label"/> </fd:label> <fd:datatype base="date"> <fd:convertor type="formatting" style="short"/> </fd:datatype></fd:output>
<fd:output id="shipping-date"> <fd:label> <i18n:text key="shipping-date.label"/> </fd:label> <fd:datatype base="date"> <fd:convertor type="formatting" style="short"/> </fd:datatype></fd:output>
The form definition file
The <fd:action> widget An action button other than standard "submit"
Allows various actions to be taken on the form "action-command" defines the event name
<fd:repeater id="contacts"> <fd:field id="firstname"> <fd:label>Firstname</fd:label> <fd:datatype base="string"/> </fd:field> <fd:field id="lastname"> <fd:label>Lastname</fd:label> <fd:datatype base="string"/> </fd:field> <fd:booleanfield id="select"/></fd:repeater>
<fd:action id="add" action-command="add-contact"> <fd:label>Add contact</fd:label></fd:action>
<fd:action id="remove" action-command="remove-selected-contacts"> <fd:label>Remove selected contacts</fd:label></fd:action>
<fd:repeater id="contacts"> <fd:field id="firstname"> <fd:label>Firstname</fd:label> <fd:datatype base="string"/> </fd:field> <fd:field id="lastname"> <fd:label>Lastname</fd:label> <fd:datatype base="string"/> </fd:field> <fd:booleanfield id="select"/></fd:repeater>
<fd:action id="add" action-command="add-contact"> <fd:label>Add contact</fd:label></fd:action>
<fd:action id="remove" action-command="remove-selected-contacts"> <fd:label>Remove selected contacts</fd:label></fd:action>
The form template
The big picture (again)
The form template
Role of the Cocoon FormsTransformer "Expand" all ft: elements
<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"> <head> <title>Registration form</title> </head> <body> <h1>Registration</h1> <ft:form-template action="registration" method="POST"> <ft:widget-label id="name"/> <ft:widget id="name"/> <br/> <ft:widget-label id="email"/> <ft:widget id="email"/> <br/> …/… <input type="submit"/> </ft:form-template> </body></html>
<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"> <head> <title>Registration form</title> </head> <body> <h1>Registration</h1> <ft:form-template action="registration" method="POST"> <ft:widget-label id="name"/> <ft:widget id="name"/> <br/> <ft:widget-label id="email"/> <ft:widget id="email"/> <br/> …/… <input type="submit"/> </ft:form-template> </body></html>
<html xmlns:ft="http://apache.org/cocoon/forms/1.0#instance"> <head> <title>Registration form</title> </head> <body> <h1>Registration</h1> <fi:form-template action="registration" method="POST"> Name: <fi:field id="name"> <fi:label>Name:</fi:label> <fi:value>Cocoon</fi:value> </fi:field> <br/> Email address: <fi:widget id="email"> <fi:label>Email address:</fi:label> <fi:value>foo</fi:value> <fi:validation-message> Invalid email address </fi:validation-message> </fi:widget> <br/> …/… <input type="submit"/> </fi:form-template> </body></html>
<html xmlns:ft="http://apache.org/cocoon/forms/1.0#instance"> <head> <title>Registration form</title> </head> <body> <h1>Registration</h1> <fi:form-template action="registration" method="POST"> Name: <fi:field id="name"> <fi:label>Name:</fi:label> <fi:value>Cocoon</fi:value> </fi:field> <br/> Email address: <fi:widget id="email"> <fi:label>Email address:</fi:label> <fi:value>foo</fi:value> <fi:validation-message> Invalid email address </fi:validation-message> </fi:widget> <br/> …/… <input type="submit"/> </fi:form-template> </body></html>
Validation failed
The form template
Role of the FormsTransformer Expand all "ft" elements in their "fi" counterpart
ft = Cocoon Forms template fi = Cocoon Forms instance
Output of the transformer goes to stylingProvided : HTML stylingOther stylings are possible (e.g. WML) Cocoon Forms does not hardcode the presentation !
The form template
The <ft:form-template> element Setup the transformer context
Retrieve the Form object from the environment
The <ft:widget-label> element Copies the content of <fd:label>
Used to separate label output from the full widget
The form template
The <ft:widget> element Produces the corresponding widget instance
Markup depends on the actual widgetFor fields : <fi:label>, <fi:value>, <fi:selection-list>
<ft:widget> can contain styling informationDrives the styling stylesheet Contents of <fi:styling> depends on the styling !
<ft:widget id="fourchars"> <!-- particular styling for the enumeration --> <fi:styling list-type="listbox" listbox-size="4"/></ft:widget>
<ft:widget id="fourchars"> <!-- particular styling for the enumeration --> <fi:styling list-type="listbox" listbox-size="4"/></ft:widget>
The form template
The <ft:repeater-widget> element Iterates on the contents of a <fd:repeater>
Use to build e.g. tablesProduces indexed names for each iteration
<ft:repeater-widget-label> Gets the label of a child of the repeater
Useful for table headers
The form template
The <ft:repeater-widget> element
<table> <tr> <th> <ft:repeater-widget-label id="contacts" widget-id="firstname"/> </th> <th> <ft:repeater-widget-label id="contacts" widget-id="email"/> </th> </tr> <ft:repeater-widget id="contacts"> <tr> <td> <ft:widget id="firstname"/> </td> <td> <ft:widget id="email"/> </td> </tr> </ft:repeater-widget></table>
<table> <tr> <th> <ft:repeater-widget-label id="contacts" widget-id="firstname"/> </th> <th> <ft:repeater-widget-label id="contacts" widget-id="email"/> </th> </tr> <ft:repeater-widget id="contacts"> <tr> <td> <ft:widget id="firstname"/> </td> <td> <ft:widget id="email"/> </td> </tr> </ft:repeater-widget></table>
<table> <tr> <th>Name</th> <th>Email address</th> </tr> <tr> <td> <fi:widget id="contacts.0.firstname"> <fi:label>Name</fi:label> <fi:value>Harry</fi:value> </fi:widget> </td> <td> <fi:widget id="contacts.0.email"> <fi:label>Email address</fi:label> <fi:value>[email protected]</fi:value> </fi:widget> </td> </tr> <tr> <td> <fi:widget id="contacts.1.firstname"> <fi:label>Name</fi:label> <fi:value>Anakin</fi:value> </fi:widget> </td> <td> <fi:widget id="contacts.1.email"> <fi:label>Email address</fi:label> <fi:value>[email protected]</fi:value> </fi:widget> </td> </tr></table>
<table> <tr> <th>Name</th> <th>Email address</th> </tr> <tr> <td> <fi:widget id="contacts.0.firstname"> <fi:label>Name</fi:label> <fi:value>Harry</fi:value> </fi:widget> </td> <td> <fi:widget id="contacts.0.email"> <fi:label>Email address</fi:label> <fi:value>[email protected]</fi:value> </fi:widget> </td> </tr> <tr> <td> <fi:widget id="contacts.1.firstname"> <fi:label>Name</fi:label> <fi:value>Anakin</fi:value> </fi:widget> </td> <td> <fi:widget id="contacts.1.email"> <fi:label>Email address</fi:label> <fi:value>[email protected]</fi:value> </fi:widget> </td> </tr></table>
Built in HTML styling
The provided stylesheets »forms-field-styling.xsl"
Only styles basic inputs (fields & actions) many styling variants
»forms-advanced-field-styling.xsl »Advanced group layout fieldsets, tab panels, overlapping panels
Built in HTML styling
Field styling Basic styling : html input <fi:styling type="…">
"password" <input type="password">"hidden" <input type="hidden">"textarea" <textarea>"date" date popup
Built in HTML styling
Fields with a <selection-list> Basic styling : drop-down menu <fi:styling type="…">
"listbox" <select><option>…"radio" <input type="radio">
• Additional "orientation" attribute (horizontal/vertical)
<multivaluefield> Basic styling : check-boxes
Built in HTML styling
<fi:group> Instance-only widget providing high-level styling
No corresponding <fd:> nor <ft:>Contains items that will be laid out
<fi:group> <fi:label>Profile header</fi:label> <fi:styling type="fieldset" layout="columns"/> <fi:items> <ft:widget id="revision"/> <ft:widget id="identification"/> <ft:widget id="name"/> <ft:widget id="author"/> <ft:widget id="classID"/> <ft:widget id="releaseDate"> <fi:styling type="date"/> </ft:widget> <ft:widget id="additional-info"/> </fi:items></fi:group>
<fi:group> <fi:label>Profile header</fi:label> <fi:styling type="fieldset" layout="columns"/> <fi:items> <ft:widget id="revision"/> <ft:widget id="identification"/> <ft:widget id="name"/> <ft:widget id="author"/> <ft:widget id="classID"/> <ft:widget id="releaseDate"> <fi:styling type="date"/> </ft:widget> <ft:widget id="additional-info"/> </fi:items></fi:group>
Built in HTML styling
<fi:group> styling Contents rendering
"layout" attribute : "columns" Automatic 2-column layout of widgets and labels
type="fieldset"
layout="columns"
<fi:group> <fi:label>Profile header</fi:label> <fi:styling type="fieldset" layout="columns"/> <fi:items> <ft:widget id="revision"/> [...] </fi:items></fi:group>
<fi:group> <fi:label>Profile header</fi:label> <fi:styling type="fieldset" layout="columns"/> <fi:items> <ft:widget id="revision"/> [...] </fi:items></fi:group>
Built in HTML styling
<fi:group> styling Container rendering
"type" attribute : "fieldset", "tabs", "choice" Tabs defined with CSS
type="choice"
type="tabs"
Note : items in "tabs" and "choice" are most often subgroups
Binding : linking forms to application data
An additional binding definition file Associates widget names to XPath expressions on the
data modelExample : binding to an XML document
<fb:context xmlns:fb="http://apache.org/cocoon/forms/1.0#binding" xmlns:fd="http://apache.org/cocoon/forms/1.0#definition" path="/data/user" >
<fb:value id="email" path="email" readonly="true"/>
<fb:value id="number" path="number/@value"> <fd:convertor datatype="long"/> </fb:value>
<fb:value id="choose" path="choose/@value"> <fd:convertor datatype="boolean"/> </fb:value></fb:context>
<fb:context xmlns:fb="http://apache.org/cocoon/forms/1.0#binding" xmlns:fd="http://apache.org/cocoon/forms/1.0#definition" path="/data/user" >
<fb:value id="email" path="email" readonly="true"/>
<fb:value id="number" path="number/@value"> <fd:convertor datatype="long"/> </fb:value>
<fb:value id="choose" path="choose/@value"> <fd:convertor datatype="boolean"/> </fb:value></fb:context>
Set the context of included
paths
Associates a widget to a
path
Read-only
widget
Binding convertor (XML
is text)
Usage in flow script
The form.js library Provides a Form class
Constructor takes a form definition file Method Form.showForm() to display the form
Does not return until validation is ok !! Internal loop on sendPageAndWait()
function edit_header() { var form = new Form("forms/profile-header-def.xml"); form.showForm("view-profile-header.html", {foo: bar});
var revision-date = form.getModel().releaseDate; ... sendDialog("Thanks a lot");}
function edit_header() { var form = new Form("forms/profile-header-def.xml"); form.showForm("view-profile-header.html", {foo: bar});
var revision-date = form.getModel().releaseDate; ... sendDialog("Thanks a lot");}
Application data for the
view
Getting form data
Usage in flow script
Advanced features Business-level validation
Because not all constraints can be in the definition file Knowing the <fd:action> that was hit
function edit_header() { var limit = Application.getLimit(); var form = new Form("forms/profile-header-def.xml"); form.validator = function(form) { var dateWidget = form.getWidget("releaseDate"); if (dateWidget.getValue() > limit) { dateWidget.setValidationError("Out of limits"); return false; } return true; }
form.showForm("view-profile-header.html", {foo: bar}); if (form.submitID == 'cancel') { sendDialog("Action cancelled"); } else { var revision-date = form.getModel().releaseDate; ... sendDialog("Thanks a lot"); }}
function edit_header() { var limit = Application.getLimit(); var form = new Form("forms/profile-header-def.xml"); form.validator = function(form) { var dateWidget = form.getWidget("releaseDate"); if (dateWidget.getValue() > limit) { dateWidget.setValidationError("Out of limits"); return false; } return true; }
form.showForm("view-profile-header.html", {foo: bar}); if (form.submitID == 'cancel') { sendDialog("Action cancelled"); } else { var revision-date = form.getModel().releaseDate; ... sendDialog("Thanks a lot"); }}
Form is redisplayed if result is
false
Test of the submit button
Usage in flow script
Advanced features Binding form and application data
function edit_header() { var data = Application.getData(); var form = new Form("forms/profile-header-def.xml");
form.createBinding("forms/profile-header-binding.xml"); form.load(data);
form.showForm("view-profile-header.html", {foo: bar});
form.save(data);
sendDialog("Thanks a lot");}
function edit_header() { var data = Application.getData(); var form = new Form("forms/profile-header-def.xml");
form.createBinding("forms/profile-header-binding.xml"); form.load(data);
form.showForm("view-profile-header.html", {foo: bar});
form.save(data);
sendDialog("Thanks a lot");}
Putting it all together : the sitemap
HTTP method selection GET method : start the flow POST method : continue Advantages
The form's action is "" (submit to the same URL)Continuation management is hidden
<map:match pattern="edit_*.html"> <map:select type="method"> <!-- GET : start the flow for this screen --> <map:when test="GET"> <map:call function="editor_{1}"/> </map:when> <!-- POST (form submission) : continue the flow --> <map:when test="POST"> <map:call continuation="{request-param:continuation-id}"/> </map:when> </map:select></map:match>
<map:match pattern="edit_*.html"> <map:select type="method"> <!-- GET : start the flow for this screen --> <map:when test="GET"> <map:call function="editor_{1}"/> </map:when> <!-- POST (form submission) : continue the flow --> <map:when test="POST"> <map:call continuation="{request-param:continuation-id}"/> </map:when> </map:select></map:match>
Putting it all together : the sitemap
View pipeline Use of JXTemplate generator
Allows the use of application data passed to showForm()
Or just a plain file generator, using the template
<map:match pattern="view-*.html"> <map:generate type="jxtemplate" src="forms/{1}-tmpl.xml"/> <map:transform type="forms"/> <map:transform type="i18n"/> <map:transform type="xslt" src="resources/editor-layout.xsl"/> <map:serialize type="html"/></map:match>
<map:match pattern="view-*.html"> <map:generate type="jxtemplate" src="forms/{1}-tmpl.xml"/> <map:transform type="forms"/> <map:transform type="i18n"/> <map:transform type="xslt" src="resources/editor-layout.xsl"/> <map:serialize type="html"/></map:match>
<map:match pattern="view-*.html"> <map:generate type="file" src="forms/{1}-tmpl.xml"/> <map:transform type="forms"/> <map:transform type="i18n"/> <map:transform type="xslt" src="resources/editor-layout.xsl"/> <map:serialize type="html"/></map:match>
<map:match pattern="view-*.html"> <map:generate type="file" src="forms/{1}-tmpl.xml"/> <map:transform type="forms"/> <map:transform type="i18n"/> <map:transform type="xslt" src="resources/editor-layout.xsl"/> <map:serialize type="html"/></map:match>
Conclusion
A powerful form frameworkRich datatypes and validation rulesEasy extension to specific needs
Flow script makes it really easy Future work
Client-side validation Better event handling system Overall stabilization
Detailed documentation on http://wiki.cocoondev.org/Wiki.jsp?page=CForms
Summary
Good for complex / multi forms Widget Oriented Approach
Custom validation rules Dependencies Strongly type data
Separation of declarations and templates Modular Reuse
Business Integration via Flow I18n Support
Tales from the operation room
8 simple rules for building a manageable Cocoon applications
Cocoon and manageability
Cocoon is a huge beast to master Things tend to get out of hands quickly The real Cocoon advantage is about increased
manageability So plan for it
Plan, plan, plan
Don’t start coding, start sketching Customize the Cocoon environment:
Be ruthless in excluding blocks Have a solid build system Use mount-table wisely Do version control local.build.properties and local.blocks.properties
Manage configuration files
You will need to modify configuration files: Use Cocoon’s own XConfTool to patch Cocoon
configurations Separate configuration (patch) files by functionality Have further configuration files to overcome the current
Cocoon blocks limitations (why do you need JMS to access WebDAV?)
Don’t trust components in Cocoon’s main sitemap, declare them again
Relocation is not an option
Users might install your application in a number of ways: Directly from the webapp root From a context path As a sub-sitemap
Resource resolving needs to take this into account: Use a mount-point variable
<map:generate src="{global:mount-point}/some/where.xml">
Perform calculations (upcoming Cocoon versions)
<map:generate src="{request:sitemapURIPrefix}/some/where.xml">
Samples are just samples
Don’t rely on them for any functionality
Import the required resources, don’t link to them
This is especially true for Cocoon Forms Newer Cocoon versions have Forms resources packaged
in a jar file However, XSLTs are still included in samples
Separate application and user concerns
In most applications, users are supposed to manage configuration files
Keep user-accessible resources separated: Client-side CSS, JS, images Consider xsl:import (with performance in mind) as
override mechanism
Package and ship
Ideally, package everything in a jar file
Yes, even sitemaps: <map:mount src=”resource://com/pnetx/app/sitemap.xmap"/>
Don’t forget: Error management (map:handle-errors) Error logging (logkit.xconf)
Wrapping up
Long journey, and bumpy ride Yet barely scratched the surface More Cocoon presentations during ApacheCon:
DE1348: Tools for Content Management and Publishing in Apache: An Overview
DE1258: Single Source Publishing with Apache Forrest DE1221: Cocoon - One Hour Portal DE1244: Developing Enterprise Web Applications with
Cocoon and Spring DE1284: Creating Print on Demand solutions with Cocoon,
FOP, and Lucene DE1272: Powering High-volume web sites with
Lenya/Cocoon and mod_cache
Thank you!
Questions?