Requirements
An integrated Version of eXist (XML Database) in Cocoon; to achieve this see EXistInCocoon, EXistAsBlock and CocoonInstall at the eXist Wiki. There is also a short Cocoon Introduction which also introduces eXist in pdf Format
- Knowledge of Cforms and the binding framework
- Some java and flowscript knowledge
Cocoon Part
Sitemap
<map:transformer logger="sitemap.transformer.xmldb" name="xmldb" src="org.apache.cocoon.transformation.XMLDBTransformer">
<!-- Driver class (optional). Uncomment if you want XMLDBTransformer
to create and register XML:DB database. Keep commented out if
database created somewhere else (for example, by XML:DB source factory)
<driver>org.apache.xindice.client.xmldb.embed.DatabaseImpl</driver>
-->
<base>xmldb:xindice-embed:///db</base>
</map:transformer>
<!--#######################################################-->
<!-- eXist's XMLDBTransformer -->
<!--#######################################################-->
<map:transformer name="xmldb" src="org.exist.cocoon.XMLDBTransformer"/>
</map:transformers>
...
<!-- Control Flow -->
<map:flow language="javascript">
<!-- Flow will use the javascript functions defined in the files here -->
<map:script src="javascript/flow/test.js"/>
</map:flow>
...
<!-- call to js functions -->
<map:match pattern="test">
<map:act src="xmldb:exist:///db" type="xmldb-login">
<map:call function="test"/>
</map:act>
<!-- no session found: display login form -->
<map:generate src="data/xml/login.xml" type="jx">
<map:parameter name="requri" value="{request:requestURI}" />
</map:generate>
<map:transform src="stylesheets/xsl/doc2html-2.xsl"/>
<map:serialize type="xhtml"/>
</map:match>
...
<!-- Cforms pipelines -->
<map:match pattern="*-display-pipeline">
<map:generate src="cforms/template/{1}_tpl.xml"/>
<map:transform type="forms"/>
<map:transform type="i18n">
<map:parameter name="locale" value="de-DE"/>
</map:transform>
<map:transform src="resources/forms-samples-styling.xsl"/>
<map:serialize/>
</map:match>
<map:match pattern="*-success-pipeline">
<map:generate src="data/html/ok.html"/>
<map:serialize type="html" />
</map:match>
Flow
cocoon.load("resource://org/apache/cocoon/forms/flow/javascript/Form.js");
function test() {
var form = new Form("cforms/definition/test_def.xml");
var bindingURI = "cforms/binding/test_bind.xml";
form.createBinding(bindingURI);
var docBaseURI = "xmldb:exist:///db/test/";
var docName = "test.xml";
var document = loadDocument(docBaseURI + docName);
form.load(document);
form.showForm("test-display-pipeline");
form.save(document);
// saveDocument(document, docURI);
saveDocumentAuth(document, "test.xml", docBaseURI, "user", "pass", "true");
var model = form.getModel();
var bizdata = { "username" : model.name }
cocoon.sendPage("test-success-pipeline", bizdata);
}
/**
* Translate source path into target path so we keep a clean source XML.
*/
function makeTargetURI(path) {
var sfx = ".xml";
var newSfx = "-result.xml";
var newPath = path;
if (path.match(/^.*\.xml$/)) {
newPath = path.substring(0, path.length - ".xml".length);
}
return newPath + newSfx;
}
function loadDocument(uri) {
var parser = null;
var source = null;
var resolver = null;
try {
parser = cocoon.getComponent(Packages.org.apache.excalibur.xml.dom.DOMParser.ROLE);
resolver = cocoon.getComponent(Packages.org.apache.cocoon.environment.SourceResolver.ROLE);
source = resolver.resolveURI(uri);
var is = new Packages.org.xml.sax.InputSource(source.getInputStream());
is.setSystemId(source.getURI());
return parser.parseDocument(is);
} finally {
if (source != null)
resolver.release(source);
cocoon.releaseComponent(parser);
cocoon.releaseComponent(resolver);
}
}
function saveDocumentAuth(document, docname, dburi, user, password, overwrite) {
// exist API http://exist.sourceforge.net/api/index.html
// XMLUtils API http://cocoon.apache.org/2.1/apidocs/
importClass(Packages.org.xmldb.api.DatabaseManager);
importClass(Packages.org.xmldb.api.base.Collection);
importClass(Packages.org.xmldb.api.modules.XMLResource);
importClass(Packages.org.apache.excalibur.xml.sax.SAXParser);
// Error Messages
var _errmsg = null;
var _errdesc = null;
// set the document xml data
var xmldata = Packages.org.apache.cocoon.xml.XMLUtils.serializeNode(document);
// Collection (org.xmldb.api.base.Collection)
var collection = null;
// ressource (org.xmldb.api.modules.XMLResource)
var _resource = null;
try {
collection = DatabaseManager.getCollection(dburi, user, password);
collection.setProperty("sax-document-events", "false");
try {
_resource = collection.getResource(docname);
if(_resource != null) {
if(overwrite.equals("true")) {
collection.removeResource(_resource);
_resource = collection.createResource( docname, "XMLResource" );
_resource.setContent(xmldata);
collection.storeResource(_resource);
} else {
_errmsg = "Resource " + docname + " does already exist";
_errdesc = "Resource " + docname + " does already exist";
cocoon.log.error(_errmsg);
cocoon.log.error(_errdesc);
}
} else {
_resource = collection.createResource( docname, "XMLResource" );
_resource.setContent(xmldata);
collection.storeResource(_resource);
}
} catch(_ex) { // XMLDBException
_errmsg = _ex.getMessage;
_errdesc = "Failed to store document" + docname;
cocoon.log.error(_errmsg);
cocoon.log.error(_errdesc);
}
} catch(_e) { // XMLDBException
_errmsg = _e.toString();
_errdesc = "Failed to get collection " + dburi;
cocoon.log.error(_errmsg);
cocoon.log.error(_errdesc);
}
}
function saveDocument(document, uri) {
var source = null;
var resolver = null;
var outputStream = null;
try {
resolver = cocoon.getComponent(Packages.org.apache.cocoon.environment.SourceResolver.ROLE);
source = resolver.resolveURI(uri);
var tf = Packages.javax.xml.transform.TransformerFactory.newInstance();
if (source instanceof Packages.org.apache.excalibur.source.ModifiableSource
&& tf.getFeature(Packages.javax.xml.transform.sax.SAXTransformerFactory.FEATURE)) {
outputStream = source.getOutputStream();
var transformerHandler = tf.newTransformerHandler();
var transformer = transformerHandler.getTransformer();
transformer.setOutputProperty(Packages.javax.xml.transform.OutputKeys.INDENT, "true");
transformer.setOutputProperty(Packages.javax.xml.transform.OutputKeys.METHOD, "xml");
transformerHandler.setResult(new Packages.javax.xml.transform.stream.StreamResult(outputStream));
var streamer = new Packages.org.apache.cocoon.xml.dom.DOMStreamer(transformerHandler);
streamer.stream(document);
} else {
throw new Packages.org.apache.cocoon.ProcessingException("Cannot write to source " + uri);
}
} finally {
if (source != null)
resolver.release(source);
cocoon.releaseComponent(resolver);
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
} catch (error) {
cocoon.log.error("Could not flush/close outputstream: " + error);
}
}
}
}
Cforms
Definition
<?xml version="1.0" ?>
<fd:form xmlns:fd="http://apache.org/cocoon/forms/1.0#definition">
<fd:widgets>
<fd:repeater id="users" initial-size="1">
<fd:label>Users</fd:label>
<fd:widgets>
<fd:output id="id">
<fd:datatype base="long"/>
</fd:output>
<fd:field id="name" required="true">
<fd:label>Name:</fd:label>
<fd:datatype base="string"/>
<fd:validation>
<fd:length min="2"/>
</fd:validation>
</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:field>
<fd:booleanfield id="select">
<fd:label>Select</fd:label>
</fd:booleanfield>
</fd:widgets>
<fd:validation>
[...]
</fd:validation>
</fd:repeater>
<fd:repeater-action id="adduser" command="add-row" repeater="users">
<fd:label>Add User</fd:label>
</fd:repeater-action>
<fd:repeater-action id="removeuser" command="delete-rows" repeater="users" select="select">
<fd:label>Remove selected users</fd:label>
</fd:repeater-action>
</fd:widgets>
</fd:form>
Template
<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template" xmlns:fi="http://apache.org/cocoon/forms/1.0#instance">
<head>
<title>Testform</title>
</head>
<body>
<h1>Test</h1>
<ft:form-template action="#{$continuation/id}.continue" method="POST">
<ft:widget-label id="users"/>
<ft:repeater-size id="users"/>
<table border="1">
<tr>
<th><ft:repeater-widget-label id="users" widget-id="name"/></th>
<th><ft:repeater-widget-label id="users" widget-id="email"/></th>
<th><ft:repeater-widget-label id="users" widget-id="select"/></th>
</tr>
<ft:repeater-widget id="users">
<tr>
<td><ft:widget id="id"/><ft:widget id="name"/></td>
<td><ft:widget id="email"/></td>
<td><ft:widget id="select"/></td>
</tr>
</ft:repeater-widget>
<tr>
<td colspan="3">
<ft:widget id="adduser"/>
<ft:widget id="removeuser"/>
</td>
</tr>
</table>
<input type="submit"/>
</ft:form-template>
</body>
</html>
Binding
<?xml version="1.0"?>
<fb:context
xmlns:fb="http://apache.org/cocoon/forms/1.0#binding"
xmlns:fd="http://apache.org/cocoon/forms/1.0#definition"
path="/" >
<!--
- Repeater requires unique identification mechanism of the row-nodes.
- (it is of course possible to implement other binding strategies)
-->
<fb:repeater id="users"
parent-path="users"
row-path="user">
<fb:identity>
<fb:value id="id" path="@id" direction="load">
<!-- optional convertor to use for mapping the unique id -->
<fd:convertor datatype="long" />
</fb:value>
</fb:identity>
<fb:on-bind>
<!-- executed on updates AND right after the insert -->
<fb:javascript id="id" path="@id" direction="save">
<!-- FIXME: Need Bugfix, this is probably NOT failsave for multi-user accesses (@id)! -->
<fb:save-form>
var appValue = jxpathPointer.getValue();
if (appValue == '') {
var user = widget;
var form = widget.getForm();
var users = user.lookupWidget("/users");
var max = 0;
for (var i=0; i < users.getSize(); i++) {
if (max < users.getRow(i).lookupWidget("id").getValue() ) {
max = users.getRow(i).lookupWidget("id").getValue();
}
}
var newId = Number(max) +1;
jxpathPointer.setValue(newId.toFixed(0));
}
</fb:save-form>
</fb:javascript>
<fb:value id="name" path="name" />
<fb:value id="email" path="email" />
</fb:on-bind>
<fb:on-delete-row>
<fb:delete-node />
</fb:on-delete-row>
<fb:on-insert-row>
<fb:insert-node>
<user id="">
<name/>
<email/>
</user>
</fb:insert-node>
</fb:on-insert-row>
</fb:repeater>
</fb:context>