| <?xml version="1.0" encoding="utf-8"?> |
| <!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.0//EN" "../dtd/document-v10.dtd"> |
| <document> |
| <header> |
| <title>Xindice within the XMLForm Framework</title> |
| <authors> |
| <person name="Josema Alonso" email="josema@simbiosystems.com"/> |
| </authors> |
| </header> |
| <body> |
| <s1 title="Notice"> |
| <p>This How-To is based on components included in the Cocoon 2.1 |
| distribution. If you do not have this version, you can obtain it from |
| the <link href="../index.html">Apache Cocoon</link> web site. |
| </p> |
| <p> Some user accessible points in the Cocoon 2.1 distribution should be |
| considered "alpha". This means that the developer team is not |
| investing _any_ effort to provide backward compatibility between alpha |
| releases for these parts. This software will continue to be released as |
| "alpha" until its code, schemas, and APIs are considered stable. |
| </p> |
| <p>Until then, there will be no warranty that newer versions will |
| maintain backward compatibility for such parts, even in the most simple |
| cases. Of course Cocoon will be compatible to latest release, 2.0.x |
| release. However, once "beta" status is reached, backward |
| incompatible changes will be made only when absolutely necessary to |
| reach "final" status. </p> |
| <p>The Cocoon development team understands the importance of reliable |
| software as well protecting user investments through the creation of a |
| solid development platform that does not change. On the other hand, |
| the Cocoon project is a pioneer in many fields. Most of the technologies |
| it uses are at a "working draft" phase only. Thus, reliability |
| cannot be guaranteed before the software achieves its "final" |
| status. </p> |
| <p>Until then, no effort will be provided to guarantee backward |
| compatibility for any parts considered alpha. </p> |
| <p>You have been warned.</p> |
| </s1> |
| <s1 title="Overview"> |
| <p> |
| This How-To shows you how to use Xindice as the repository for |
| XML resources from the XMLForm Framework. It requires prior |
| knowledge of Cocoon XMLForm, XSLT, Schematron, Xindice and the |
| XMLDB API. |
| </p> |
| </s1> |
| <s1 title="Purpose"> |
| <p> |
| You will learn how to build a simple wizard type XMLForm that |
| stores XML data into a Xindice collection. |
| </p> |
| </s1> |
| <s1 title="Intended Audience"> |
| <p> |
| Cocoon users who want to learn how to store data obtained from |
| XMLForms into Xindice. |
| </p> |
| </s1> |
| <s1 title="Prerequisites"> |
| <p>Cocoon must be running on your system. The steps below have been |
| tested with Cocoon 2.1-dev.</p> |
| <p>You will need the following:</p> |
| <ul> |
| <li>A servlet engine such as Tomcat.</li> |
| <li>JDK 1.2 or later</li> |
| <li>Xindice 1.0 installed (create a collection named |
| <code>Artist</code>)</li> |
| </ul> |
| <p>Cocoon 2.1 CVS to be installed with the command:</p> |
| <source>build -Dinclude.webapp.libs=true webapp</source> |
| <p> |
| You will need to understand and be familiar with XSL, XForms, |
| XPath, Schematron and Xindice. Some knowledge about JXPath and the |
| XMLDB API would be helpful, too. If you are unfamiliar with these |
| technologies, it is advised that you learn these related concepts |
| first. If you are unfamiliar with XMLForm, check out the |
| <link href="xmlform-wizard/howto-xmlform-wizard.html">XMLForm |
| Wizard How-To</link> first. |
| </p> |
| </s1> |
| <s1 title="Steps"> |
| <p> |
| We will follow the needed steps in order to add a document like |
| the one below to a Xindice collection named <code>Artist</code>. |
| </p> |
| <source><![CDATA[ |
| <Artist id="pearljam"> |
| <Name>Pearl Jam</Name> |
| </Artist>]]></source> |
| <p> |
| We will get the identifier and name data using a XMLForm and |
| store them in Xindice. We will build this XMLForm very similar to |
| the one in the XMLForm Wizard How-To. |
| </p> |
| <s2 title="1. Building the XMLForm files"> |
| <p> |
| Create the files and name them as specified below. |
| </p> |
| <s3 title="start.xform"> |
| <source><![CDATA[ |
| <?xml version="1.0"?> |
| <document> |
| <h1>This is the New Artist Wizard!</h1> |
| <info>Steps from here on, will let you insert a new |
| Artist in the database. |
| </info> |
| <h3> |
| <a href="Artist.xform?cocoon-action-start=true"> |
| Start! |
| </a> |
| </h3> |
| </document>]]></source> |
| </s3> |
| <p/> |
| <s3 title="artist.xform"> |
| <source><![CDATA[ |
| <?xml version="1.0"?> |
| <document xmlns:xf="http://apache.org/cocoon/xmlform/1.0"> |
| <xf:form id="artist-insert" view="artist" |
| action="Artist.xform" method="post"> |
| <xf:caption>New Artist</xf:caption> |
| <error> |
| <xf:violations class="error"/> |
| </error> |
| <xf:textbox ref="/Artist/@id"> |
| <xf:caption>Artist identifier:</xf:caption> |
| </xf:textbox> |
| <xf:textbox ref="/Artist/Name"> |
| <xf:caption>Artist Name:</xf:caption> |
| </xf:textbox> |
| <xf:submit id="prev" class="button"> |
| <xf:caption>Prev</xf:caption> |
| <xf:hint>Go to previous page</xf:hint> |
| </xf:submit> |
| <xf:submit id="next" class="button"> |
| <xf:caption>Next</xf:caption> |
| <xf:hint>Go to next page</xf:hint> |
| </xf:submit> |
| </xf:form> |
| </document>]]></source> |
| </s3> |
| <p/> |
| <s3 title="end.xform"> |
| <source><![CDATA[ |
| <?xml version="1.0"?> |
| <document> |
| <h1>You have reached the last page!</h1> |
| <info> |
| You have inserted a New Artist successfully. |
| </info> |
| <h3> |
| <a href="Artist.xform">Go to home page.</a> |
| </h3> |
| </document>]]></source> |
| </s3> |
| <p/> |
| <s3 title="error.xform"> |
| <source><![CDATA[ |
| <?xml version="1.0"?> |
| <document> |
| <h1> |
| You have reached the last page of the New Artist Wizard! |
| </h1> |
| <info> |
| There have been problems and the Artist could not be added to the |
| database. Please try again. |
| </info> |
| <h3> |
| <a href="Artist.xform">Please, start again.</a> |
| </h3> |
| </document>]]></source> |
| </s3> |
| <p/> |
| </s2> |
| <s2 title="2. Validation"> |
| <p> |
| For the sake of simplicity we just validate one property against |
| one condition. We require the identifier to be at least two |
| characters in length; the validation file, |
| <strong><code>artist-validator.xml</code></strong> is as follows: |
| </p> |
| <source><![CDATA[ |
| <?xml version="1.0"?> |
| <schema ns="http://xml.apache.cocoon/xmlform" |
| xmlns="http://www.ascc.net/xml/schematron"> |
| <phase id="artist"> |
| <active pattern="artval"/> |
| </phase> |
| <pattern name="Artist Identifier Validation" id="artval"> |
| <rule context="/Artist/@id"> |
| <assert test="string-length(.) > 1"> |
| Artist Name should be at least 2 characters. |
| </assert> |
| </rule> |
| </pattern> |
| </schema>]]></source> |
| <p/> |
| <s3 title="Extended Validation"> |
| <p> |
| There could be more complicated rules in Schematron but we |
| could also require the identifier to be unique in the |
| database. In this case we should query the database and see |
| if it already exists. If so, a new violation can be added to |
| the form. Since these kinds of violations are out of the scope |
| of Schematron, these operations should be accomplished in the |
| Action using Java code. |
| </p> |
| </s3> |
| <p/> |
| </s2> |
| <s2 title="3. The Model"> |
| <p> |
| Here is where we will take a different approach to the one in |
| the XMLForm Wizard How-To. We will not use a separate Bean. |
| In order to have an XML document model we will create an XML |
| file with the empty structure we want to fill with data from |
| the form. Create the file |
| <strong><code>artist-model.xml</code></strong> and fill it with: |
| </p> |
| <source><![CDATA[ |
| <Artist id=""> |
| <Name></Name> |
| </Artist>]]></source> |
| <p> |
| Persistence for the data will be accomplished by using a |
| <link href="http://jakarta.apache.org/commons/jxpath/apidocs/org/apache/commons/jxpath/Container.html">JXPath Container</link>. |
| The XML document model created above will be loaded into the |
| Container. We will use the <code>xmlform-model</code> parameter |
| in the sitemap to point to the XML file. |
| The Container is created and manipulated by the Action in its |
| <code>getFormModel()</code> method, so we need to override it |
| and write it this way (we will see the whole Action in the next |
| step): |
| </p> |
| <source><![CDATA[ |
| /** |
| * Extract xmlform-model parameter and |
| * instantiate a new form model from it. |
| */ |
| protected Object getFormModel() { |
| //to load the XML model |
| Container DOMModel = null; |
| Source modelSrc = null; |
| //this parameter holds the name of the empty XML document |
| //representing the model |
| String modelFileName = |
| getParameters().getParameter("xmlform-model", null); |
| if(modelFileName==null) return null; |
| try { |
| modelSrc = getSourceResolver().resolveURI(modelFileName); |
| DOMModel = new XMLDocumentContainer(new |
| StreamSource(modelSrc.getInputStream())); |
| return DOMModel; |
| } |
| catch ( Exception e) { |
| throw new CascadingRuntimeException( |
| " Failed instantiating form model ", e ); |
| } |
| finally { |
| getSourceResolver().release(modelSrc); |
| } |
| }]]></source> |
| <p/> |
| </s2> |
| <s2 title="4. The Action"> |
| <p> |
| In this Action we have integrated the Xindice handling code, |
| getting the data from the model and storing it in Xindice. |
| The Action that controls this form is |
| <strong><code>ArtistAction.java</code></strong>: |
| </p> |
| <source><![CDATA[ |
| package com.simbiosystems.cocoon.xmlform.xindice.howto; |
| |
| import java.util.Arrays; |
| import java.util.Map; |
| |
| import javax.xml.transform.stream.StreamSource; |
| import org.apache.avalon.framework.CascadingRuntimeException; |
| import org.apache.cocoon.acting.AbstractXMLFormAction; |
| import org.apache.cocoon.components.validation.Violation; |
| import org.apache.cocoon.components.xmlform.Form; |
| import org.apache.cocoon.components.xmlform.FormListener; |
| import org.apache.commons.jxpath.Container; |
| import org.apache.commons.jxpath.XMLDocumentContainer; |
| import org.apache.excalibur.source.Source; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Node; |
| |
| /** |
| * This action handles XMLForms for the Artist data |
| */ |
| public class ArtistAction extends AbstractXMLFormAction implements |
| FormListener { |
| // different form views participating in the form |
| final String VIEW_START = "start"; |
| final String VIEW_ARTIST = "artist"; |
| final String VIEW_END = "end"; |
| final String VIEW_ERROR = "error"; |
| // action commands used in the wizard |
| final String CMD_START = "start"; |
| final String CMD_NEXT = "next"; |
| final String CMD_PREV = "prev"; |
| //constant for the XML manipulation, it holds the full name of the |
| //collection to be used for storing the data |
| final String xindiceSubCol = "/Artist"; |
| |
| /** |
| * Extract xmlform-model action parameter and |
| * instantiate a new form model from it. |
| * |
| * In this case it uses a JXPath Container as the model and |
| * there is no need for a separate model Bean. |
| * |
| * @return Form the form object this action works with. |
| * |
| */ |
| protected Object getFormModel() { |
| //to load the XML model |
| Container DOMModel = null; |
| Source modelSrc = null; |
| //this parameter holds the name of the empty XML document |
| //representing the model |
| String modelFileName = |
| getParameters().getParameter("xmlform-model", null); |
| if(modelFileName==null) return null; |
| try { |
| modelSrc = getSourceResolver().resolveURI(modelFileName); |
| DOMModel = new XMLDocumentContainer(new |
| StreamSource(modelSrc.getInputStream())); |
| return DOMModel; |
| } |
| catch ( Exception e) { |
| throw new CascadingRuntimeException( |
| " Failed instantiating form model ", e ); |
| } |
| finally { |
| getSourceResolver().release(modelSrc); |
| } |
| } |
| |
| /** |
| * Invoked after form population |
| * Take appropriate action based on the command |
| * |
| */ |
| public Map perform () { |
| // set the page control flow parameter |
| // according to the validation result |
| if ( getCommand().equals(CMD_NEXT) && |
| getForm().getViolations () != null ) { |
| // errors, back to the same page |
| return page( getFormView() ); |
| } |
| else { |
| // validation passed |
| // continue with control flow |
| // clear validation left overs in case the user |
| // did not press the Next button |
| getForm().clearViolations(); |
| // get the user submitted command |
| String command = getCommand(); |
| // get the form view which was submitted |
| String formView = getFormView(); |
| // apply control flow rules |
| if (formView.equals (VIEW_ARTIST)) { |
| if (command.equals(CMD_NEXT)) { |
| //extended validation |
| //test if the ID already exists in the DB |
| String artistName = (String)getForm().getValue("/Artist/@id"); |
| try { |
| XindiceManager xi = new XindiceManager(); |
| Node result = xi.find(xindiceSubCol, |
| "/Artist[@id='"+ artistName +"']", |
| "Artist"); |
| //if we do not get null the element with that ID |
| //already existed and we add the violation |
| if (result!=null) { |
| Violation v = new Violation(); |
| v.setMessage("already exists in the database, |
| please choose another one"); |
| v.setPath("/Artist/@id"); |
| Violation[] va = { v }; |
| getForm().addViolations(Arrays.asList((Object[])va)); |
| //the ID already exists, back to the same |
| //page to correct the error |
| return page(VIEW_ARTIST); |
| } |
| } |
| catch (Exception e) { |
| getLogger().error("Cannot establish a connection to the DB", e); |
| } |
| //everything went fine, add the document to the database |
| try { |
| addDocument(); |
| } |
| catch(Exception e) { |
| //there were errors, send it to the error page |
| getLogger().error("Cannot add DOM document to the database"); |
| return page(VIEW_ERROR); |
| } |
| return page( VIEW_END); |
| |
| } |
| if (command.equals(CMD_PREV)) { |
| return page(VIEW_START); |
| } |
| } |
| } |
| // should never reach this statement |
| return page( VIEW_START ); |
| } |
| |
| /** |
| * The first callback method which is called |
| * when an action is invoked. |
| * |
| * It is called before population. |
| * @return null if the Action is ready to continue. |
| * an objectModel map which will be returned |
| */ |
| protected Map prepare() { |
| if ( getCommand() == null ) { |
| return page(VIEW_START); |
| } |
| else if ( getCommand().equals(CMD_START)) { |
| // reset state by removing old form if one exists |
| Form.remove( getObjectModel(), getFormId() ); |
| getForm().addFormListener( this ); |
| return page(VIEW_ARTIST); |
| } |
| // get ready for action |
| // if not ready return page("whereNext"); |
| return null; |
| } |
| |
| /** |
| * Add the document to the database |
| * |
| */ |
| public void addDocument() throws Exception { |
| try { |
| //add the document to the database |
| XindiceManager xi = new XindiceManager(); |
| //needs the DocumentRoot of the Container as a DOM Node |
| xi.add(xindiceSubCol, |
| ((Document) |
| (((XMLDocumentContainer) |
| (getForm().getModel())).getValue())).getDocumentElement(), |
| null); |
| } |
| catch (Exception e) { |
| getLogger().error("DOM Document could not be created", e); |
| throw e; |
| } |
| } |
| }]]></source> |
| <p> |
| We had to use an ugly casting mechanism in the |
| <code>addDocument</code> method but that is what we have by now. |
| In future JXPath versions this will be easier to accomplish. |
| </p> |
| <s3 title="The helper class"> |
| <p> |
| In order to make this work we need to use a helper class. |
| This class uses the XMLDB API to connect to Xindice and make |
| the operations available to the Action. You should extend it |
| to add more operations. The helper class |
| <strong><code>XindiceManager.java</code></strong> is as follows: |
| </p> |
| <source><![CDATA[ |
| /** |
| * Helper class for Xindice related operations |
| * |
| */ |
| package com.simbiosystems.cocoon.xmlform.xindice.howto; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import org.w3c.dom.DOMImplementation; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.xmldb.api.DatabaseManager; |
| import org.xmldb.api.base.Collection; |
| import org.xmldb.api.base.Database; |
| import org.xmldb.api.base.Resource; |
| import org.xmldb.api.base.ResourceIterator; |
| import org.xmldb.api.base.ResourceSet; |
| import org.xmldb.api.base.XMLDBException; |
| import org.xmldb.api.modules.XMLResource; |
| import org.xmldb.api.modules.XPathQueryService; |
| |
| public class XindiceManager { |
| |
| private static final String driver = |
| "org.apache.xindice.client.xmldb.DatabaseImpl"; |
| private static final String rootCollection = "xmldb:xindice:///db/"; |
| |
| /** |
| * Constructor |
| * |
| */ |
| public XindiceManager() { |
| |
| } |
| |
| /** |
| * Search for a document in the DB. If not found, return null. |
| * |
| * @param subCol name of the subCollection to query if any. If blank |
| * or null, queries go against the rootCollection. |
| * @param xpath XPath expression for the query, if none, it returns |
| * the whole Collection |
| * @param resultRootelement name of the root element for the DOM |
| * document which will wrap the results |
| * @return a DOM Node with the matched documents |
| * |
| */ |
| public Node find(String subCol, String xpath, String resultRootElement) |
| throws Exception { |
| |
| //prepare DOM document |
| DOMImplementation impl; |
| DocumentBuilder builder; |
| try { |
| // Find the implementation |
| DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
| factory.setNamespaceAware( false ); |
| factory.setValidating ( false ); |
| builder = factory.newDocumentBuilder(); |
| impl = builder.getDOMImplementation(); |
| } |
| catch (Exception ex) { |
| throw new RuntimeException("[XindiceManager.find]: " + |
| "Failed to initialize DOM factory. Root cause: \n" + ex); |
| } |
| //create the Document which will hold the results |
| Document resultDoc = impl.createDocument(null, resultRootElement, null); |
| Node root = resultDoc.getDocumentElement(); |
| |
| //And now the Xindice part |
| Collection col = null; |
| String strData = null; |
| |
| try { |
| Class c = Class.forName(driver); |
| |
| Database database = (Database) c.newInstance(); |
| DatabaseManager.registerDatabase(database); |
| |
| String localCol = rootCollection + subCol; |
| |
| col = DatabaseManager.getCollection(localCol); |
| |
| //Make the XPath query and get the resources |
| XPathQueryService service = |
| (XPathQueryService) col.getService("XPathQueryService", "1.0"); |
| ResourceSet resultSet = service.query(xpath); |
| // Iterate the xpath results and add each of them to the main Document |
| ResourceIterator iterator = resultSet.getIterator(); |
| |
| //if not resources are present, just return null |
| if(!iterator.hasMoreResources()) return null; |
| |
| while(iterator.hasMoreResources()) { |
| Resource r = iterator.nextResource(); |
| |
| Element resElement = |
| ((Document)((XMLResource)r).getContentAsDOM()).getDocumentElement(); |
| |
| // Remove unwanted attributes |
| resElement.removeAttribute("src:col"); |
| resElement.removeAttribute("src:key"); |
| resElement.removeAttribute("xmlns:src"); |
| |
| // Add this result to the root element |
| Element importedElement = |
| (Element)resultDoc.importNode(resElement, true); |
| root.appendChild(importedElement); |
| } |
| //return the Document root |
| return root; |
| } |
| catch (Exception e) { |
| System.err.println("[XindiceManager.find]: Find Exception occured :" + |
| e.getMessage()); |
| throw e; |
| } |
| finally { |
| if (col != null) { |
| col.close(); |
| } |
| } |
| } |
| |
| /** |
| * Add a document to the DB. If a key is not passed, it generates a |
| * unique one. |
| * This version uses a DOM Document. |
| * @param subCol name of the subCollection in which to add the resource |
| * if any. If blank or null, insertions go against the rootCollection. |
| * @param document DOM document to be inserted |
| * @param key unique key for the resource to be created. |
| * If none, one is created on the fly |
| */ |
| public void add(String subCol, Node document, String key) |
| throws Exception { |
| Collection col = null; |
| Node xmldoc = null; |
| try { |
| Class c = Class.forName(driver); |
| |
| Database database = (Database) c.newInstance(); |
| DatabaseManager.registerDatabase(database); |
| |
| col = DatabaseManager.getCollection(rootCollection + subCol); |
| |
| XMLResource document_ = (XMLResource) col.createResource(null, |
| "XMLResource"); |
| document_.setContentAsDOM(document); |
| col.storeResource(document_); |
| } |
| catch (XMLDBException e) { |
| System.err.println("[XindiceManager.add]: Add Exception occured " + |
| e.errorCode); |
| throw e; |
| } |
| finally { |
| if (col != null) { |
| col.close(); |
| } |
| } |
| } |
| }]]></source> |
| <p/> |
| </s3> |
| </s2> |
| <s2 title="5. The Sitemap"> |
| <p> |
| Remember you should have the <code>XMLFormTransformer</code> |
| defined like this: |
| </p> |
| <source><![CDATA[ |
| <map:transformer name="xmlform" |
| src="org.apache.cocoon.transformation.XMLFormTransformer" |
| logger="xmlform"/>]]></source> |
| <p> |
| Then, you must declare the Action to be used in the |
| corresponding section: |
| </p> |
| <source><![CDATA[ |
| <map:action logger="xmlform" |
| name="ArtistAction" |
| src="com.simbiosystems.cocoon.xmlform.xindice.howto.ArtistAction"/> |
| ]]></source> |
| <p/> |
| <s3 title="The Pipeline"> |
| <p> |
| The following pipeline will process requests to our Form: |
| </p> |
| <source><![CDATA[ |
| <!-- XMLForms<->Xindice pipeline --> |
| <map:pipeline> |
| <map:match pattern="Artist.xform"> |
| <map:act type="ArtistAction"> |
| <!-- XMLForm parameters for the Action --> |
| <!-- Notice how we use an XML file as the model --> |
| <map:parameter name="xmlform-validator-schema-ns" |
| value="http://www.ascc.net/xml/schematron"/> |
| <map:parameter name="xmlform-validator-schema" |
| value="artist/artist-validator.xml"/> |
| <map:parameter name="xmlform-id" value="artist-insert"/> |
| <map:parameter name="xmlform-scope" value="session"/> |
| <map:parameter name="xmlform-model" value="artist/artist-model.xml"/> |
| <!-- XMLForm document, {page} comes from Action --> |
| <map:generate src="artist/{page}.xform"/> |
| </map:act> |
| <!-- populating the doc with model instance data --> |
| <map:transform type="xmlform"/> |
| <!-- look and feel of the form controls --> |
| <map:transform src="styles/wizard2html.xsl"/> |
| <!-- Transforming the XMLForm controls to HTML --> |
| <map:transform src="styles/xmlform2html.xsl"/> |
| <!-- sending the HTML back to the browser --> |
| <map:serialize type="html"/> |
| </map:match> |
| </map:pipeline>]]></source> |
| <p> |
| Depending on where you have Cocoon installed and where you |
| have configured the files in this how-to, you could make a |
| request to a URL like |
| <code>http://localhost:8080/cocoon/Artist.xform</code> |
| and start using the Form. |
| </p> |
| </s3> |
| </s2> |
| </s1> |
| <s1 title="Final Considerations"> |
| <p> |
| Making the operations from the Action using the helper class |
| seems to be the easiest way. You can think of other ways or |
| other operations using other Xindice tools available in Cocoon |
| such as the pseudo protocol. For example, you could use it to |
| query the DB for data that could be stored in a sitemap parameter. |
| You could then get the data from the Action and use it to fill a |
| selectbox in the form. |
| </p> |
| <p> |
| We did not mentioned other operations such as updates. This can |
| be also accomplished this way. For example, if you want to edit |
| the data we just stored, you could load it in the Container at |
| the beginning by using the find method of the helper class. |
| This way you get a filled form in the next step ready for editing. |
| Other ways or interacting with the repository include the XMLDB |
| Transformer. Since it uses XUpdate alike syntax, you could format |
| a XML String for it in the last step of the Action, making it |
| available to the sitemap then, so the Transformer could get it |
| and make the operations. |
| </p> |
| <p> |
| We are sure you can think of more different ways and encourage |
| you to contribute them if you have tested it successfully. |
| This how-to was refactored quite a bit from an original version |
| which used a regular Javabean as a wrapper for a DOM Node where |
| the data were persisted. This new version is much more elegant |
| and short. If you want to check differences, an |
| <link href="http://wiki.cocoondev.org/Wiki.jsp?page=XMLFormXindiceOldVersion">old version at Cocoon Wiki</link> |
| still exists. |
| </p> |
| </s1> |
| <s1 title="Summary"> |
| <p> |
| This How-To makes possible the use of Xindice from XMLForms in |
| order to add data to the repository. You learned how to connect |
| to the DB from the Action, and how to make complex validation |
| using information stored in the DB. Remember that everything you |
| have just built is reusable for different user agents. You would |
| only need to use |
| <link href="../userdocs/concepts/matchers_selectors.html">matchers |
| and selectors</link> and to choose the appropriate transformation. |
| We hope this how-to was helpful for you. |
| </p> |
| </s1> |
| <s1 title="References"> |
| <anchor id="references"/> |
| <p> |
| This how-to would not be possible without some ideas exchanged |
| with Ivelin Ivanov in the |
| <link href="http://cocoon.apache.org/community/mail-lists.html">Cocoon Users</link> list and |
| privately. |
| </p> |
| </s1> |
| <s1 title="Comments"> |
| <p> |
| Care to comment on this How-To? Got another tip? |
| Help keep this How-To relevant by passing along any useful |
| feedback to the author, |
| <link href="mailto:josema|at|simbiosystems.com">Josema Alonso</link> |
| or via the |
| <link href="http://cocoon.apache.org/community/mail-lists.html">Cocoon Users</link> mail list. |
| </p> |
| </s1> |
| <s1 title="Revisions"> |
| <p> |
| 2003-02-04: First version contributed by Josema Alonso. |
| </p> |
| </s1> |
| </body> |
| </document> |