This documentation relates to an old version of the Nuxeo Platform (5.5). You may want to check the latest technical documentation.

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: synchronized Nuxeo 5.5 documentation with the new 5.6 documentation
Section
Column
width70%
Info
titleAlternative title

A tutorial about the hard way - configure the platform via XML only.

The goal of this tutorial is to give you a fast introduction into the most important steps if you want to customize and/or test Nuxeo in process oriented environments.

The tutorial consists of:

  • a description of the use case,
  • a description of the filing (structure) plan,
  • the real tutorial with integrated tests of your understanding.

The tutorial includes a JAR archive with all necessary stuff. Even the solutions are integrated. So you can check the solutions if you have problems (wink)

Attachments
Warning
titleTODO
  1. Replace Makefile with a Maven configuration.
  2. The plugin numberedheadings is not available.
  3. The tutorial does not demonstrate the usage of schemas.
  4. The tutorial does not demonstrate the usage of versions.
  5. The tutorial does not demonstrate the usage of status.
  6. The tutorial is only tested with Nuxeo 5.5.
Column
Panel
bgColor#FFFFFF
titleOn this page
Table of Contents

Introduction

Use Case

The use case is the management of the parking permissions at area 51.
So this is a quite common task for the public service.

Filing (structure) plan

The general folder structure is as follows:

...

Nevertheless this is an example for everybody and readability is preferred over correctness.

Tutorial

META-INF/MANIFEST

The MANIFEST is the configuration file of the bundle. It must be at the first position in the JAR archive. The explanation is a historical one and quite simple. Just imagine tapes and not random access memory. Now do you want to rewind the whole tape to know what
is stored on it and where?

...

The components are the files which are loaded by Nuxeo. If your archive includes a configuration file which is missing in the MANIFEST then it will be ignored without any error message from Nuxeo.

Tip
titleWant to know more?

More information about the MANIFEST are available from the How-to create an empty bundle recipe.

...

So what is your actual/first task? Please replace the makefile with a Maven configuration (and a description) and contribute it back (wink)

Definition of a new folder type

The folders create the general structure.
A folder itself usually does not contain a document.
It contains other folders and documents.
Additionally a folder is described by some meta data.

Nuxeo clearly separates the structural definition and the user interface of a document.
So both parts must be configured separately.

The examples utilize the process.
The process is the management or better only the granting/denial of parking permissions at area 51. First we define a folder which
is only used for the management of the customer requests.

Creating the Process folder

Defining the document type (process-type.xml)


PAP means public administration process. It is used to guarantee the uniqueness of the component names.

Structural configuration

First we need to define the folder type itself. This means which (meta) data is needed in the folder and which features are available for the folder itself. So let's extend org.nuxeo.ecm.core.schema.TypeService with the component org.nuxeo.dev.cookbook.example.pap.process.type.

Code Block
xml
titleOSGI-INF/process-type.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process.type">

  <extension target="org.nuxeo.ecm.core.schema.TypeService" point="doctype">
    <doctype name="DevCookbookExamplePapProcess" extends="Folder">
      <schema name="common"/>
      <schema name="dublincore"/>
    </doctype>
  </extension>

</component>

The new type is only a special folder with most basic stuff.

Configuring the user interface (process-ui-type.xml)

Now, the user interface must be configured for this special folder.

Tip
titleMeta Data

Libraries especially in research environments like universities heavily depend on meta data standards. If your content should be usable, searchable and referenceable then it is strongly recommended that you use standards like Dublin Core. Please see here for more informations:

User interface

The user interface configuration must basically include two things - the layout and the parents.

First the layout stuff defines how a type is displayed. It does not only create a label and a description.
The category defines for example in which section you will see the the document type if you click on the add document button.
You can find a detailed description here Document types.

The parents are defined to give the user interface a hint where the the new document type should be placed.
The example process is only allowed directly at the root of a workspace.

Code Block
xml
titleOSGI-INF/process-ui-type.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process.ui-type">

  <extension target="org.nuxeo.ecm.platform.types.TypeService" point="types">

    <type id="DevCookbookExamplePapProcess">
      <label>Public Administration Process</label>
      <description>This is an example for a basic process in the public administration.</description>
      <category>Collaborative</category>
      <default-view>view_documents</default-view>
      <layouts mode="any">
        <layout>heading</layout>
      </layouts>
      <layouts mode="edit">
        <layout>heading</layout>
        <layout>dublincore</layout>
      </layouts>
      <icon>icons/folder.gif</icon>
      <bigIcon>icons/folder_100.png</bigIcon>
    </type>

    <!-- only allow the basic process folder in the root of a workspace -->

    <type id="Workspace">
      <subtypes>
        <type>DevCookbookExamplePapProcess</type>
      </subtypes>
    </type>

  </extension>

</component>

The label defines the name of the component in the user interface. The category defines the group of objects in the interface where you can find the component.

So let's test your knowledge with the first tasks:

  1. Create a new workspace.
  2. Click on Create a new document.
  3. Look for the document type Public Administration Process
  4. Create a new process.

...

Actions

If you create a new type then you usually do this to better control the activities and the workflows inside this type.
The next step is to get some button to perform some actions or operation chains.

The first activity of every good paper tiger is the creation of a good description of the process. Mainly this includes a short name and a description of the content (e.g. parking permissions). Additionally requests are only accepted if there is a description. This means that the button has to do more than only one operation. Et voila, we need an operation chain.

Code Block
xml
titleOSGI-INF/process-actions.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process.actions">

  <extension target="org.nuxeo.ecm.platform.actions.ActionService" point="actions">

    <!-- first you have to create the description of the process -->

    <action id="newDevCookbookExamplePapProcessDescription"
            link="#{operationActionBean.doOperation('CreateDevCookbookExamplePapProcessDescription')}"
            enabled="true"
            order="10"
            label="Create a process description"
            icon="/icons/action_add.gif">
      <category>SUBVIEW_UPPER_LIST</category>
      <filter id="newDevCookbookExamplePapProcessDescription">
        <rule grant="true">
          <permission>AddChildren</permission>
          <type>DevCookbookExamplePapProcess</type>
        </rule>
      </filter>
    </action>

  </extension>

</component>

...

Name

Description

id

Name

order

A hint where to place the button if there are several actions.

label

The displayed name.

icon

The used basic icon.

link

The operation function call with the correct parameters.

The operation is a specialty speciality here because it is the operation to call a chain. The parameter is the name of the chain.

...

The filter is a control that defines when to display an action. If you want to reuse a filter, you can do this by only specifying the matching id. Here we only want to see the button if we have the permission to create new documents in this folder (e.g. a description or a folder with the requests) and if the folder is of type DevCookbookExamplePapProcess.

Next step: define a chain

The description of the process

What is the job of the chain? First create a description, then create the folder for the requests and open the description to fill it with some content.

First we have to define the type and user interface for a special document type, the description. This is not quite different from a folder but it is documented here for completeness. Then the implementation of a chain is demonstrated.

Creating the Description document type

Defining the Description document type (process_description-type.xml)

Okay, let's see what's special here.

...

Training lesson

Before we start with the tests please remember that you can always look into the JAR archive to check your solution.

A request folder will contain the form from the customer, the approval or denial of a parking permission.
The parent of a request folder is a DevCookbookExamplePapProcessRequests folder.
Every customer form will be in a separate request folder.

Please create and fill the following files:

  1. process_request-type.xml
  2. process_request-ui-type.xml

Definition of a new file type

A normal file (which is not a folder) usually contains only meta data or additionally a real file.
So if you think in terms of a file system hierarchy then this is the place where to store your data.

If you want to store a text or PDF file in an ECM system then you store some raw data together with some meta data.
A file system knows such meta data too but it is very limited (e.g. ACLs, ownership, some timestamps, etc.).
If you use an ECM system then you can freely define which meta data you need and want to store.

Structural configuration

The structural definition is a little bit more complex here. Some more schemas are used to get additional features like unique identifiers and typical file meta data. The really interesting stuff are the facets.

Tip
titleWarnings from facets

If you look into your server log then you will usually see a warning that an unknown facet is used but this can be ignored (Nuxeo Enterprise Platform 5.5).

The facets support us with two features.
Publishable makes it possible that the description can be copied to Sections. This area is used only to make documents available. You cannot edit documents in sections. So if you want to publish the description (e.g. to people who work in area 51), then a place in Sections would be right.
Versionable allows you to maintain different versions of the same document. This could be useful if you need to know all old descriptions. The versioning of Nuxeo is described later in this tutorial.

Code Block
xml
titleOSGI-INF/process_description-type.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-description.type">

  <extension target="org.nuxeo.ecm.core.schema.TypeService" point="doctype">
    <doctype name="DevCookbookExamplePapProcessDescription" extends="Document">
      <schema name="common"/>
      <schema name="uid"/>
      <schema name="dublincore"/>
      <schema name="file"/>
      <facet name="Versionable"/>
      <facet name="Publishable"/>
    </doctype>
  </extension>

</component>

The description gets a uid and the regular properties of a file. The really interesting stuff are the facets. If you look into your server log then you will usually see a warning that an unknown facet is used but this can be ignored (Nuxeo Enterprise Platform 5.5).

The facet support us with two features.
Publishable makes it possible that the description can be copied to Sections. This area is used only to make documents available. You cannot edit documents in sections. So if you want to publish the description (e.g. to people who work in area 51), then a place in Sections would be right.
Versionable allows you to maintain different versions of the same document. This could be useful if you need to know all old descriptions.

...

User interface

The specification of the user interface only includes some minor interesting stuff. The special stuff for files is made available
and the descriptions are description is only allowed directly in the root folder of a process.

That's it. KISS.

Code Block
xml
titleOSGI-INF/process_description-ui-type.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-description.ui-type">

  <extension target="org.nuxeo.ecm.platform.types.TypeService" point="types">

    <type id="DevCookbookExamplePapProcessDescription">
      <label>Process description</label>
      <description>The description of the process.</description>
      <category>SimpleDocument</category>
      <default-view>view_documents</default-view>
      <layouts mode="any">
        <layout>heading</layout>
        <layout>file</layout>
      </layouts>
      <layouts mode="edit">
        <layout>heading</layout>
        <layout>file</layout>
        <layout>dublincore</layout>
      </layouts>
      <icon>icons/file.gif</icon>
      <bigIcon>icons/file_100.png</bigIcon>
    </type>

    <!-- only the process folder itself should contain a process description -->

    <type id="DevCookbookExamplePapProcess">
      <subtypes>
        <type>DevCookbookExamplePapProcessDescription</type>
      </subtypes>
    </type>

  </extension>

</component>
Creating the automation chain (process_description-chains.xml)

...

Training lesson

If you want to test your understanding you can create now the following configurations:

  1. The user request form
    1. process_request_form-type.xml
    2. process_request_form-ui-type.xml
  2. The file which documents the approval of the user request
    1. process_request_approval-type.xml
    2. process_request_approval-ui-type.xml
  3. The file which documents the approval of the user request
    1. process_request_reject-type.xml
    2. process_request_reject-ui-type.xml

Please remember you can find all files in the JAR archive.
It is not necessary to do all the stuff by yourself.

Actions

Let's assume there is a customer request but there is no decision. So we need two buttons on the request form: one to approve and one to reject the request.

If you want to configure an action button on a file then this works a little bit different than on folders.
First you need to define a tab and then you can place buttons in this tab.
The example re-use the folder panel as tab.

Let's start with the approval of a request.

The category VIEW_ACTION_LIST defines a new tab in the document.
The XHTML document in the parameter link defines which template is used.

Code Block
xml
titleOSGI-INF/process_request_form-actions.xml

<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request-form.actions">

  <extension target="org.nuxeo.ecm.platform.actions.ActionService" point="actions">

    <action id="tabDevCookbookExamplePapProcessRequestDecision"
            link="/incl/document_actions.xhtml"
            enabled="true"
            order="30"
            label="Decision"
            icon="/icons/file.gif">
      <category>VIEW_ACTION_LIST</category>
      <filter id="tabDevCookbookExamplePapProcessRequestDecision">
        <rule grant="true">
          <type>DevCookbookExamplePapProcessRequestForm</type>
          <!-- permission:write -->
        </rule>
      </filter>
    </action>

The category SUBVIEW_UPPER_LIST is the usual category from the folders.
This works here because the template is the default folder template.

Code Block
xml

    <action id="approveDevCookbookExamplePapProcessRequest"
            link="#{operationActionBean.doOperation('ApproveDevCookbookExamplePapProcessRequest')}"
            enabled="true"
            order="20"
            label="Approve the request"
            icon="/icons/action_add.gif">
      <category>SUBVIEW_UPPER_LIST</category>
      <filter id="approveDevCookbookExamplePapProcessRequest">
        <rule grant="true">
          <type>DevCookbookExamplePapProcessRequestForm</type>
          <!-- permission:write -->
        </rule>
      </filter>
    </action>

  </extension>

</component>

Now there is a tab called Decision with a button Approve request.

Tip
titleI18N - Internationalization

Please note that you can translate the labels. You can translate in the usual message.po files. The problem is that there is only one central file. So you must change the master file.

Training lesson

Okay, the task to implement the reject button should be very simple.
Please create the reject button (perhaps on another tab).

Operation chains

Operation chains are a very powerful tool. Every operation has an input and an output.
If you want to connect them like a pipe then you just have to ensure that the input and output are compatible.
This sounds complicated but it is very easy because there are not so many allowed input and output types:

  • void
  • document
  • blob

The only problem is that even a list of documents is represented as a blob.
So you cannot directly use it but below you will find a workaround.

Basic stuff

The example shows the operation chain for the creation of the description of the process.
The configured action above defined the name CreateDevCookbookExamplePapProcessDescription for this chain.
What is the job of this chain?
First create a description, second create the folder for the requests and last open the description to fill it with some content.

First things first. Let's create the description. No, just a moment.

There are two very important functions - Seam.AddErrorMeessage and Seam.AddInfoMessage.
Please always add an error message in front of every operation or functional block to ensure that you get a meaningful error message.
If the operation chain ends then please add an info message which reports the success.
This costs some time but it enormous helpful if something fails and the user can give you meaningful error messages because they learned that every operation results in a message.

The following code snip is quite simple. It creates the description and give it the name description.

Code Block
xml
titleOSGI-INF/process_description-chains.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-description.chains">

  <extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent" point="chains">

    <chain id="CreateDevCookbookExamplePapProcessDescription">
      <!--
        Create a fresh description
        Create folder requests
        Open the description
      -->
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">The description could not be created.</param>
      </operation>
      <operation id="Document.Create">
        <param type="string" name="type">DevCookbookExamplePapProcessDescription</param>
        <param type="string" name="properties">dc:title=description</param>
      </operation>

Second Next the resulting document is loaded to the user interface. So if the chain is completed, the user sees it.

Code Block
xml
...
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot load the description to the UI.</param>
      </operation>
      <operation id="Seam.NavigateTo">
      </operation>

Perhaps you will ask now, why you do the last step first and what happens if something fails later?
The document is only available now as an input. I could store it in a variable but this is not necessary.
Operation chains work as a single transaction. So if something fails, nothing happened.

After the description is done, there is still one thing missing - the folder for the requests. So let's create it.

Code Block
xml
...
      <operation id="Document.GetParent">
      </operation>
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">The folder for the requests could not be created.</param>
      </operation>
      <operation id="Document.Create">
        <param type="string" name="type">DevCookbookExamplePapProcessRequests</param>
        <param type="string" name="properties">dc:title=requests</param>
      </operation>

Do you see the mistake? Just remember, first things first!

Finally the success message is setup. Please note that you can configure it at any place in the chain. The functions to set an error or an information just copy the input to the output. They are so called void functions.

Code Block
xml
...
      <operation id="Seam.AddInfoMessage">
        <param type="string" name="message">The description was successfully created.</param>
      </operation>
    </chain>

  </extension>

</component>

Question: Can you already execute the action now?

Next step: a folder for a customer requests

The request

The request folder and the customer form

Before we start with the tests please remember that you can always look into the JAR archive to check your solution.

First you should test your understanding of special folder types.
Second you should test your understanding of special document types.
A request folder will contain the form from the customer, the approval or denial of a parking permission.
The parent of a request folder is a DevCookbookExamplePapProcessRequests folder.
Every customer form will be in a separate request folder.

Please create and fill the following files:

  1. process_request-type.xml
  2. process_request-ui-type.xml
  3. process_request_form-type.xml
  4. process_request_form-ui-type.xml

Create a new request

...

Dynamic names / MVEL

The operation environment implements a very useful scripting engine MVEL.
So let's take a look at a simple example.

The example create a new customer request.
First we need to calculate which request it is. Please remember this is a public administration example.
So the name of 42 the 42nd request is request-42 and not request-41.

The first part includes the following steps:

...

Finally we know which request will be created the next and the parent folder is the input.
This is business as usual.

Code Block
xml
titleOSGI-INF/process_request-chains.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request.chains">

  <extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent" point="chains">

    <chain id="CreateDevCookbookExamplePapProcessRequest">
      <!--
        Input: Called from the folder with all requests
        Calculate a name for the new folder
        Create folder with title
        Create customer_form
        Open customer form
      -->
      <!-- get the folder as input -->
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot load the folder with the requests.</param>
      </operation>
      <!-- usually unnecessary -->
      <operation id="Context.FetchDocument">
      </operation>
      <!-- determine number of children -->
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot calculate the name of the new file.</param>
      </operation>
      <operation id="Context.SetInputAsVar">
        <param type="string" name="name">requests_folder</param>
      </operation>
      <operation id="Document.GetChildren">
      </operation>
      <operation id="Context.SetInputAsVar">
        <param type="string" name="name">documents</param>
      </operation>
      <operation id="Context.SetVar">
        <param type="string" name="name">request_count</param>
        <param type="object" name="value">expr:@{(Context["documents"].size()+1).toString()}</param>
      </operation>
      <operation id="Context.RestoreDocumentInput">
        <param type="string" name="name">requests_folder</param>
      </operation>

Okay, I aggree that the determination of request_count is not hundred percent intuitive and the author needed some support from the community liaison too (tongue) (thumbs up)

Nevertheless it is possible to learn a lot from this one-liner.

Second the request folder will be created. Please note the usage of the dynamically calculated request count.
If you use variable then always use the context. The other stuff is quite intuitive.

Code Block
xml
      <!-- create structure -->
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot create a new request folder.</param>
      </operation>
      <operation id="Document.Create">
        <param type="string" name="type">DevCookbookExamplePapProcessRequest</param>
        <param type="string" name="properties">expr:dc:title=request-@{Context["request_count"]}</param>
      </operation>

...

Code Block
xml
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot create a new customer form.</param>
      </operation>
      <operation id="Document.Create">
        <param type="string" name="type">DevCookbookExamplePapProcessRequestForm</param>
        <param type="string" name="properties">dc:title=customer_form</param>
      </operation>
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot load the new customer form.</param>
      </operation>
      <operation id="Seam.NavigateTo">
      </operation>
      <operation id="Seam.AddInfoMessage">
        <param type="string" name="message">The new request was successfully created.</param>
      </operation>
    </chain>

  </extension>

</component>

Next step: buttons on documents

Handle the request

There is now a request but there is no decision. So we need two buttons on the request form.
The important thing is that this works different from folders.
First you need to define a tab and then you can place buttons in this tab.
The example re-use the folder panel as tab.

Let's start with the approval of a request.

Approve a request

The document definition is just another document configuration.
So please check the JAR archive or see it as a training lesson.
There must be three files:

Training lessons

If you want to do some training then you can write some chains for the approval or denial of the customer requests.
You can create the according documents (e.g. a ticket for the car window) or you can send an email to the entrance (or an invoice to planet Mars).

Versioning

If you use a document management system or a similar system then you expect that documents have versions.
If you are a software developer then you are familiar with checkouts and commits.

If you approve a request then the customer form should get a new final version.
The following chain which implements the approval creates such a new major version.

Code Block
xml
titleOSGI-INF/process_request_approval-

...

chains.xml

...

The real work is the configuration of an action button in a document type.

The category VIEW_ACTION_LIST defines a new tab in the document.
The XHTML document in the parameter link defines which template is used.

process_request_form-actions
Code Block
xmltitle

<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request-approval.chains">

  <extension target="org.nuxeo.ecm.core.operation.OperationServiceComponent" point="chains">

    <chain id="ApproveDevCookbookExamplePapProcessRequest">
      <!--
        Create new major version
        Create an approval
        Remove write permission on request form
        Open the approval
      -->
      <operation id="Document.CheckOut">
      </operation>
      <operation id="Document.CheckIn">
        <param type="string" name="version">major</param>
        <param type="string" name="comment">The request was approved.</param>
      </operation>
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">The approval document could not be created.</param>
      </operation>
      <operation id="Document.GetParent">
      </operation>
      <operation id="Document.Create">
        <param type="string" name="type">DevCookbookExamplePapProcessRequestApproval</param>
        <param type="string" name="properties">dc:title=approval</param>
      </operation>
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot load the approval to the UI.</param>
      </operation>
      <operation id="Seam.NavigateTo">
      </operation>
      <operation id="Seam.AddErrorMessage">
        <param type="string" name="message">Cannot remove the write permissions from the customer form.</param>
      </operation>
      <operation id="Document.GetParent">
      </operation>
      <!-- this does not work if you work as Administrator ;) -->
      <operation id="Document.SetACE">
        <param type="string" name="permission">WriteProperties</param>
        <param type="string" name="user"></param> <!-- this needs a real user -->
        <param type="boolean" name="grant">false</param>
      </operation>
      <operation id="Seam.AddInfoMessage">
        <param type="string" name="message">The approval was successfully created.</param>
      </operation>
    </chain>

  </extension>

</component>

The initial check out is necessary to get a working copy of the document.
You can only check in documents which are a working copy.

The check in requires at minimum the parameter version which differs between minor or major check ins.
The final check after an approval is of course a major version.
The comment is optional which is a horror for people who uses revision control systems for software development.
One of the most important rule is: No commits without comments.

Training lesson

Please create the configuration process_request_reject-chains.xml as a test of your understanding.

Management of states (life cycle)

Perhaps you note the title of the section is not Status management.
The reason is simple: Nuxeo enforces a life cycle model which is some kind of a finite state machine.
If you need some kind of state for a folder or a document, then you have to define a life cycle including the allowed state transitions.

The definition is quite straight forward:

Code Block
xml
titleOSGI-INF/process_request-lifecycle.xml
<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request-form.actionslifecycle">

  <extension target="org.nuxeo.ecm.platformcore.actionslifecycle.ActionServiceLifeCycleService" point="actionslifecycle">

    <action<lifecycle idname="tabDevCookbookExamplePapProcessRequestDecisionDevCookbookExamplePapProcessRequestPolicy" initial="new">

      <states>
   link="/incl/document_actions.xhtml"     <state name="new">
          <transitions>
 enabled="true"           <transition>approve</transition>
 order="30"             label="Decision"<transition>reject</transition>
          </transitions>
        </state>
        <state name="approved">
        </state>
   icon     <state name="/icons/file.gifrejected">
      <category>VIEW_ACTION_LIST</category>  </state>
      <filter id="tabDevCookbookExamplePapProcessRequestDecision"</states>

      <transitions>
        <transition name="approve" destinationState="approved">
        <rule grant="true"  <description>Approve the request.</description>
        </transition>
        <transition name="reject" destinationState="rejected">
          <type>DevCookbookExamplePapProcessRequestForm</type><description>Reject the request.</description>
        </transition>
 <!-- permission:write --     </transitions>

    </lifecycle>

  </extension>

The states are only used by Nuxeo to define the allowed transition paths.
The states approved and rejected are only specified because it gives a better overview.
The state after a transition is only taken from destinationState.
If there is a typo in a state then this is a dead end without an error message.

After the definition of the life cycle, you have to apply the life cycle to one or more document types.
You can re-use such life cycles and you can use life cycles to filter actions.
The filter configuration does not support a state filter directly but there is an operation Operation Document.Filter which supports a state filter.

Code Block
xml

...
  <extension target="org.nuxeo.ecm.core.lifecycle.LifeCycleService" point="types">
    <types>
   </rule>   <type name="DevCookbookExamplePapProcessRequest">DevCookbookExamplePapProcessRequestPolicy</type>
    </filter>types>
  </extension>

</action>component>

The category SUBVIEW_UPPER_LIST is the usual category from the folders.
This works here because the template is the default folder templateintegration of the life cycle into the configuration is simple.
The initial state is predefined in the life cycle configuration.
You have only to add a state change in the chain of the approval (Operation Document.SetLifeCycle).
The parameter is the name of the transition.

Code Block
xml
titleOSGI-INF/process_request_approval-chains.xml
...
   <action   <operation id="approveDevCookbookExamplePapProcessRequestDocument.GetParent">
      </operation>
     link="#{operationActionBean.doOperation('ApproveDevCookbookExamplePapProcessRequest')}"
            enabled="true"
      <!-- Document is now the request folder -->
      <operation id="Document.SetLifeCycle">
        <param type="string" name="value">approve</param>
      </operation>
...

Training lesson

If you want to verify your knowledge then you can create a life cycle for the request form itself.
Alternatively you can only add the state change to the reject chain.

Extension of meta data

Sometimes you need more meta informations than usual.
If you want to manage such a special place like area 51 then you need several different kinds of parking places.
You have cars, air planes, x-planes and of course ufos (wink)

Structural configuration

First you need to extend the meta data of a document type.
Such structural changes are done via XML schemas.
So let's introduce a new vehicle type:

Code Block
xml
titleschemas/process_request_form.xsd

<?xml version="1.0"?>
<xs:schema
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  targetNamespace="http://project.nuxeo.org/schemas/dev/cookbook/example/pap/process-request-form/"
  xmlns:area51="http://project.nuxeo.org/schemas/dev/cookbook/example/pap/process-request-form/"
  >

  <xs:simpleType name="vehicle_type">
    <xs:restriction base="xs:string">
      order<xs:enumeration value="20Car"/>
      <xs:enumeration value="Airplane"/>
    label  <xs:enumeration value="Approve the request"X-Plane"/>
      <xs:enumeration value="Ufo"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:element iconname="/icons/action_add.gif">
 parking_area" type="area51:vehicle_type"/>
</xs:schema>

So we have now a schema with exactly one element called parking_area.
Now need to make it available.
This requires a small extensions of the type definition:

Code Block
xml
titleOSGI-INF/process_request_form-type.xml

<?xml version="1.0"?>
<component name="org.nuxeo.dev.cookbook.example.pap.process-request-form.type">

   <category>SUBVIEW_UPPER_LIST</category>
  <extension target="org.nuxeo.ecm.core.schema.TypeService" point="schema">
    <schema name="area51" src="schemas/process_request_form.xsd" prefix="area51" />
  </extension>

  <filter<extension idtarget="approveDevCookbookExamplePapProcessRequest"org.nuxeo.ecm.core.schema.TypeService" point="doctype">
    <doctype name="DevCookbookExamplePapProcessRequestForm" extends="Document">
 <rule grant     <schema name="truecommon"/>
      <schema name="uid"/>
  <type>DevCookbookExamplePapProcessRequestForm</type>    <schema name="dublincore"/>
     <!-- permission:write -- <schema name="file"/>
        </rule><schema name="area51"/>
      </filter><facet name="Versionable"/>
    </action>doctype>
   </extension>

</component>

Next step: Create your own button

Reject a request

Okay, the final tasks should be very simple. Please create the following files:

  1. process_request_reject-type.xml
  2. process_request_reject-ui-type.xml
  3. process_request_form-actions.xml
  4. process_request_reject-chains.xml

The change is minimal. You need to import the schema via the according extension point
and you must add one line to the document type definition. That's it.

User interface

Second you must modify the user interface. This is much more tricky in this case.
You need in this case a new database table, a new widget and a new layout.
Finally you have to extend the type for the user interface itself.

Configure a new database table

The data source is called directory in Nuxeo.
Such directories can be LDAP directories or databases.
If you want to import some list data then you must store them in a CSV file and import it.

Code Block
titledirectories/vehicle_type.csv

id,label,obsolete
"Car","Car","0"
"X-Plane","X-Plane","0"
"Airplane","Airplane","0"
"Ufo","Ufo","0"

The import is quite simple:

Code Block
xml
titleOSGI-INF/process_request_form-ui-type.xml

  <extension target="org.nuxeo.ecm.directory.sql.SQLDirectoryFactory" point="directories">
    <directory name="vehicle_type">
      <schema>vocabulary</schema>
      <dataSource>java:/nxsqldirectory</dataSource>
      <cacheTimeout>3600</cacheTimeout>
      <cacheMaxSize>1000</cacheMaxSize>
      <table>area51_vehicle_type</table>
      <idField>id</idField>
      <autoincrementIdField>false</autoincrementIdField>
      <dataFile>directories/vehicle_type.csv</dataFile>
      <createTablePolicy>on_missing_columns</createTablePolicy>
    </directory>
  </extension>

Configure a new widget

After you have now a new directory which is filled with the data from a CSV file you need to create a widget.
A widget includes the form field or the field value depending on the situation.

Code Block
xml
titleOSGI-INF/process_request_form-ui-type.xml

  <extension target="org.nuxeo.ecm.platform.forms.layout.WebLayoutManager" point="widgets">

    <widget name="type_of_vehicle" type="selectOneDirectory">

      <labels>
        <label mode="any">Vehicle type</label>
      </labels>
      <translated>true</translated>
      <fields>
        <field>area51:parking_area</field>
      </fields>

      <properties widgetMode="any">
        <property name="directoryName">vehicle_type</property>
        <property name="localize">true</property>
        <property name="ordering">label</property>
      </properties>

    </widget>

The widget defines some usual stuff like name, label and meta data field id.
The interesting stuff here is the widget type.
You can find the available basic widget types at the Standard widget types.

selectOneDirectory implements a select field. The options are taken from the directory called vehicle_type.
This directory was defined above. localize means that the label is translatable.

Configure a new layout

The new wigdet is data only. It is necessary to layout the data.
This is done with the creation of a new layout:

Code Block
xml
titleOSGI-INF/process_request_form-ui-type.xml

  <require>org.nuxeo.ecm.platform.forms.layouts.webapp</require>

  <extension target="org.nuxeo.ecm.platform.forms.layout.WebLayoutManager" point="layouts">

    <layout name="area51">
      <templates>
        <template mode="any">/layouts/layout_default_template.xhtml</template>
      </templates>
      <rows>
        <row>
          <widget>type_of_vehicle</widget>
        </row>
      </rows>
    </layout> 

  </extension> 

Now we have a new layout area51 which uses the widget type_of_vehicle.

Use the new layout

The new layout area51 must be used by the user interface itself.
This is quite simple. The layout must be added to every layouts section.

Code Block
xml
titleOSGI-INF/process_request_form-ui-type.xml

      <layouts mode="edit">
        <layout>heading</layout>
        <layout>file</layout>
        <layout>dublincore</layout>
        <layout>area51</layout>
      </layouts>

Training lesson

If you want to test your knowledge and understanding then simply add a new field.
Just use your fantasy.

Tip
titleWant to see more or other features?

If you are looking for an example of another feature then please feel free to comment or change the TODO list.