Apache » Cocoon »

  Cocoon Forms
      1.0
   homepage

Cocoon Forms 1.0

Java API

Intro

CForms is implemented in Java so the natural way to access it is through its Java API. In this section we will show you how to get started. The two primary flow-solutions of Cocoon (Flowscript and Javaflow) each have an additional Forms integration part. However, here we want to show how the core Forms APIs should be used independent of specific flow solutions, and therefore we will use the very basic "Apples" flow.

Note: The Apples flow solution (contained in the "apples" block) is not officially supported by the Cocoon community. We use it here only for demonstrative purposes to illustrate the use of the Forms API.

Apples flow

If you are familiar with Apples, you can skip this section. What follows is a super-super short summary of how Apples works.

An Apple flow-controller is a Java class implementing the following interface:

package org.apache.cocoon.components.flow.apples;

public interface AppleController {
    void process(AppleRequest req, AppleResponse res) throws Exception;
}

In the sitemap, a flow-interaction is started using map:call with the function attribute:

<map:call function="some.fully.qualified.Classname"/>

This will create an instance of the specified class (which should implement the AppleController interface), associate that class with a continuation ID, and call the process method of the Apple (we refer to a specific Apples controller object simply as "Apple"). The Apple can then do whathever it wants to do, but before returning the process method should have called either the sendPage or redirectTo method on the supplied appleResponse object.

In contrast with the continuations-based flow solutions, calling sendPage or redirectTo does not stop the execution of the current method, it only configures what pipeline to call or where to redirect to in the AppleResponse object. It is only after the process method has returned that this information will be used by the Apples flow implementation.

To continue interacting with the same Apple instance, use map:call with the contination attribute:

<map:call continuation="some-continuation-id"/>

This will look up the previously created Apple and call again its process method.

This is basically it. An Apple can maintain state in its instance variables. You can think of an Apple object as a session specific for a certain interaction. It encapsulates both the state of the interaction and its corresponding flow logic in one object.

In contrast with the continuation-based flow solutions, the execution of an Apple always starts at the top of the process method. To find out what you need to do, you can simply use request information (the requested path, request parameters and the request method).

To make this overview complete, if you don't need a stateful interaction you can let your Apple implement the interface StatelessAppleController. In that case, no continuation ID will be associated with the Apple and it will be discarded directly after use.

Avalon

An Apples controller can implement the Avalon Framework interfaces to get access to its context. If you are familiar with Avalon, you can skip this section.

Cocoon internally manages components using the contracts defined by Avalon Framework. Avalon Framework defines a few interfaces that components can implement to let the container communicate with the component. An Apples controller is treated as such a component, thus an Apple can implement Avalon Framework interfaces.

For working with forms, we will need to be able to access the FormManager (and possibly the BindingManager). To get access to other components, Avalon defines the ServiceManager interface, which provides the following method:

public void service(ServiceManager serviceManager);

So if your component (an Apple in this case) implements the ServiceManager, this service method will be called during the setup of the component, and via the supplied ServiceManager object you can get access to other components running inside Cocoon. We will see below how we use this to get access to the FormManager.

Setting up a working environment

To have a comfortable work/build environment, we suggest to use the Ant script found on the wiki.

A basic Forms usage

Creating a form instance

Creating a form instance based on a form definition is done using the FormManager. Since the few lines to do this are needed each time we would like to create a form instance, we define a helper class to do this. While at it, it includes also a method to create a binding object using the BindingManager.

package my;

import org.apache.cocoon.forms.formmodel.Form;
import org.apache.cocoon.forms.FormManager;
import org.apache.cocoon.forms.binding.Binding;
import org.apache.cocoon.forms.binding.BindingManager;
import org.apache.avalon.framework.service.ServiceManager;

public class FormHelper {
    public static Form createForm(ServiceManager serviceManager,
                                  String formDefinitionFileName) throws Exception {
        FormManager formManager = null;
        try {
            formManager = (FormManager)serviceManager.lookup(FormManager.ROLE);
            return formManager.createForm(formDefinitionFileName);
        } finally {
            if (formManager != null)
                serviceManager.release(formManager);
        }
    }

    public static Binding createBinding(ServiceManager serviceManager,
                              String bindingDefinitionFileName) throws Exception {
        BindingManager bindingManager = null;
        try {
            bindingManager = (BindingManager)serviceManager.lookup(BindingManager.ROLE);
            return bindingManager.createBinding(bindingDefinitionFileName);
        } finally {
            if (bindingManager != null)
                serviceManager.release(bindingManager);
        }
    }
}

The Apple controller

Below is an Apple controller which is used to display a simple form containing just one field, "name". Once the name is entered correctly, it will redirect to google to perform a search on that name.

package my;

import org.apache.cocoon.components.flow.apples.AppleController;
import org.apache.cocoon.components.flow.apples.AppleRequest;
import org.apache.cocoon.components.flow.apples.AppleResponse;
import org.apache.cocoon.forms.formmodel.Form;
import org.apache.cocoon.forms.FormContext;
import org.apache.cocoon.environment.Request;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.avalon.framework.service.ServiceException;

import java.util.Map;
import java.util.HashMap;
import java.util.Locale;
import java.net.URLEncoder;

public class FirstFormsApple implements AppleController, Serviceable {
    private ServiceManager serviceManager;
    private boolean init = false;
    private Form form;
    private Locale locale = Locale.US;

    public void service(ServiceManager serviceManager) throws ServiceException {
        this.serviceManager = serviceManager;
    }

    public void process(AppleRequest appleRequest, AppleResponse appleResponse)
            throws Exception {
        if (!init) {
            form = FormHelper.createForm(serviceManager,
                           "resources/form/firstform_definition.xml");
            init = true;
        }

        Request request = appleRequest.getCocoonRequest();
        if (request.getMethod().equals("GET")) {
            showForm(appleResponse);
        } else if (request.getMethod().equals("POST")) {
            boolean finished = form.process(new FormContext(request, locale));
            if (!finished) {
                showForm(appleResponse);
            } else {
                String name = (String)form.getChild("name").getValue();
                appleResponse.redirectTo("http://google.com/search?q="
                                         + URLEncoder.encode(name, "UTF-8"));
            }
        } else {
            throw new Exception("Unexpected HTTP method: " + request.getMethod());
        }
    }

    private void showForm(AppleResponse appleResponse) {
        Map viewData = new HashMap();
        viewData.put("CocoonFormsInstance", form);
        viewData.put("locale", locale);
        appleResponse.sendPage("FirstFormPipe", viewData);
    }
}

Some notes:

  • if this is the first time the Apple is called, the form instance is created using the FormHelper utility class defined earlier
  • if the request method is GET, we simply show the form
  • if the request method is POST, the user has submitted the form so we let the form object process the request. This returns a boolean to indicate whether the form processing finished (finished means: all widgets validated successfully, and the reason for the form submit was not to execute some action or event handler). If not finished, we redisplay the form, otherwise, we do whatever it is we want to do with the collected form data. To keep this sample simple, we redirect to google.
It is important to notice that you can make the form definition and template much more complex (containing many & advanced widgets, with validation logic, event handlers, etc.), while the Apple can stay as simple as this.

Supporting files

For reference, we list the form definition, form template and sitemap used for this example.

form definition

<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:field>
  </fd:widgets>
</fd:form>

form template

<html xmlns:ft="http://apache.org/cocoon/forms/1.0#template"
  xmlns:fi="http://apache.org/cocoon/forms/1.0#instance"
  xmlns:jx="http://apache.org/cocoon/templates/jx/1.0">

  <jx:import uri="resource://org/apache/cocoon/forms/generation/jx-macros.xml"/>

  <head>
    <title>First form!</title>
  </head>
  <body>
    <ft:form-template action="#{$continuation/id}.continue" method="POST">
      <ft:widget-label id="name"/>
      <ft:widget id="name"/>
      <br/>
      <input type="submit"/>
    </ft:form-template>
  </body>
</html>

sitemap

<?xml version="1.0"?>
<map:sitemap xmlns:map="http://apache.org/cocoon/sitemap/1.0">

  <map:components>
    <map:transformers default="xslt">
      <map:transformer name="i18n"
        src="org.apache.cocoon.transformation.I18nTransformer">
        <catalogues default="forms">
          <catalogue id="forms" name="messages"
            location="resource://org/apache/cocoon/forms/system/i18n"/>
        </catalogues>
        <cache-at-startup>true</cache-at-startup>
      </map:transformer>
    </map:transformers>
  </map:components>

  <map:views>
  </map:views>

  <map:resources>
  </map:resources>

  <map:flow language="apples"/>

  <map:pipelines>

    <map:pipeline internal="true">
      <map:match pattern="FirstFormPipe">
        <map:generate type="jx" src="resources/form/firstform_template.xml"/>
        <map:transform type="i18n">
          <map:parameter name="locale" value="flow-attr:locale"/>
        </map:transform>
        <map:transform src="resources/xsl/forms-samples-styling.xsl"/>
        <map:serialize/>
      </map:match>
    </map:pipeline>

    <map:pipeline>
      <map:match pattern="firstform">
        <map:call function="my.FirstFormsApple"/>
      </map:match>

      <map:match pattern="*.continue">
        <map:call continuation="{1}"/>
      </map:match>

      <map:match pattern="resources/*/**">
        <map:read src="resource://org/apache/cocoon/{1}/resources/{2}"/>
      </map:match>
    </map:pipeline>

  </map:pipelines>

</map:sitemap>

Next

[TODO]

See also the javadoc of the forms package for more information on the API.

Errors and Improvements? If you see any errors or potential improvements in this document please help us: View, Edit or comment on the latest development version (registration required).