This article covers a Hippo CMS version 7.9. There's an updated version available that covers our most recent release.

How to implement a custom workflow?

Basic custom workflow

A custom workflow consists of at least:

  1. An interface that extends org.hippoecm.repository.api.Workflow.
  2. An implementation of that interface, possibly extending org.hippoecm.repository.ext.WorkflowImpl.
  3. An entry in one or more workflow categories. Normally as an XML node file. Here you configure in which situations your workflow is allowed within the context of that category. This is based on the node type of the node to perform workflow on and the role of the user wanting to do that.
  4. CND file to define the extra properties for the documents.
  5. A hippoecm-extension.xml to import during initialization of the repository the CND file and the XML node file for the entry in a workflow category.

GUI plugin

To be able to invoke the workflow from the CMS user interface, it's necessary to create a menu plugin. We'll assume that a suitable plugin has been created under the name FeaturedWorkflowPlugin.

Example custom workflow

A custom workflow module consists of the following files, which will be described below:

+ pom.xml
+ src
   + main
       + resources
            + featured.cnd
            + hippoecm-extension.xml
            + search.xml
            + workflow.xml

Custom workflow interface

package com.mycompany;
import ...

public interface FeaturedWorkflow extends Workflow {

    void feature() throws WorkflowException, RepositoryException,
                          MappingException, RemoteException;
}

The FeaturedWorkflow consists of a single workflow action, called 'feature'.

Note that the interface extends org.hippoecm.repository.api.Workflow; this is required of all workflows.

Custom workflow implementation

package com.mycompany;
import ...;

public class FeaturedWorkflowImpl extends WorkflowImpl
                                     implements FeaturedWorkflow {

    public FeaturedWorkflowImpl() throws RemoteException {
    }

    public void feature() throws WorkflowException, MappingException,
                                 RepositoryException {
        getNode().setProperty("featured:isFeatured", true);
    }
}

The implementation of the workflow is named 'FeaturedWorkflowImpl. As required, it implements the FeaturedWorkflow interface and extends WorkflowImpl, the standard implementation of the Workflow interface. The single workflow action 'feature' is implemented to do very little: just set a boolean named flag to be true. It could mean that the document that this action is performed on is considered important. There is no workflow action in this custom workflow to set the flag on false.

The boolean flag is not a property of a document so this would not change the state of the document this workflow action is performed on.

CND file

The properties used for persisting have to exist, which is defined by a CND. You could set a property that already exists in the document type of the document. Then you would not have to write a separate CND for it. But you have to be sure that other uses of the property will not interfere with your use of that property. Your custom workflow will be self-contained if you define a separate namespace containing a mixin node type.

<jcr='http://www.jcp.org/jcr/1.0'>
<nt='http://www.jcp.org/jcr/nt/1.0'>
<mix='http://www.jcp.org/jcr/mix/1.0'>
<hippo='http://www.onehippo.org/jcr/hippo/nt/2.0'>
<featured='http://www.mycompany.com/featured/nt/1.0'>

[featured:featured] mixin
- featured:isFeatured (Boolean) = 'false' mandatory autocreated

In the CND above the namespace http://www.mycompany.com/featured/nt/1.0 has prefix 'featured'. In this namespace, abbreviated as 'featured', a mixin node type is defined with local name 'featured'. The full name of the mixin node type is therefore featured:featured.

In the mixin node type featured:featured a property is defined, with local name 'isFeatured'. Its full name is therefore featured:isFeatured. This property is of type Boolean and has default value 'false'. It is mandatory and autocreated so that it is always available when needed. This property will be a property of every document that has this mixin type. This is defined in the document type of the document.

Workflow manager configuration ( workflow.xml)

<?xml version="1.0" encoding="UTF-8"?>
<sv:node xmlns:hipposys="http://www.onehippo.org/jcr/hipposys/nt/1.0"
         xmlns:frontend="http://www.onehippo.org/jcr/frontend/nt/2.0"
         xmlns:sv="http://www.jcp.org/jcr/sv/1.0"
         sv:name="featured">
  <sv:property sv:name="jcr:primaryType" sv:type="Name">
    <sv:value>hipposys:workflowcategory</sv:value>
  </sv:property>
  <sv:node sv:name="featured">
    <sv:property sv:name="jcr:primaryType" sv:type="Name">
      <sv:value>frontend:workflow</sv:value>
    </sv:property>
    <sv:property sv:name="hipposys:classname" sv:type="String">
      <sv:value>com.mycompany.FeaturedWorkflowImpl</sv:value>
    </sv:property>
    <sv:property sv:name="hipposys:display" sv:type="String">
      <sv:value>Featured workflow</sv:value>
    </sv:property>
    <sv:property sv:name="hipposys:nodetype" sv:type="String">
      <sv:value>featured:featured</sv:value>
    </sv:property>
    <sv:property sv:name="hipposys:privileges" sv:type="String">
      <sv:value>hippo:editor</sv:value>
    </sv:property>
    <sv:node sv:name="hipposys:types">
      <sv:property sv:name="jcr:primaryType" sv:type="Name">
        <sv:value>hipposys:types</sv:value>
      </sv:property>
    </sv:node>
    <sv:node sv:name="frontend:renderer">
      <sv:property sv:name="jcr:primaryType" sv:type="Name">
        <sv:value>frontend:plugin</sv:value>
      </sv:property>
      <sv:property sv:name="plugin.class" sv:type="String">
        <sv:value>com.mycompany.FeaturedWorkflowPlugin</sv:value>
      </sv:property>
    </sv:node>
  </sv:node>
</sv:node>

Above you see an XML node file for a hipposys:workflowcategory named 'featured'. This defines a context of situations (see the introduction). When this file is imported, that workflow category is created, if it did not yet exist. If the workflow category node already exists the children from this file are added to it. In the file above there is one child node named 'featured'. The type frontend:workflow indicates that the workflow also has a cms component, i.e. the menu button.

If the node to change is of the given node type and the user of the given role, then an instance of the given class is returned by the workflow manager.

The node type is specified with the property hipposys:nodetype. You should specify a node type such that each workflow action (method) of the given workflow (class) is applicable on each node of that type. In the example the node type is set to the mixin node type that holds the boolean property ' featured:isFeatured', which was used in the workflow action ' feature()'. If another workflow action would be added to the workflow and it would use a new property in the same mixin node type, everything is still fine. But if the new property would be in a new mixin node type, none of the two mixin node types could be used as value of the hipposys:nodetype property above. You would have to create an extra mixin node type that extends both.

The role of the allowed users is specified in the ' hipposys:privileges' property. The workflow class to return an instance of is specified in the ' hipposys:classname' property.

In the ' hipposys:display' property you specify how the work flow would be named when displayed (line 16-18). In the ' plugin.class' property of the ' frontend:renderer' child node you specify which plugin to use for buttons for the workflow in the CMS UI. Instead of the type 'frontend:plugin', it is also possible to use the type 'frontend:plugincluster' for the sub-node. This allows multiple plugins to be used for the same workflow.

To enable the workflow the CMS UI, it is necessary to add the workflow category name to the list of categories used in the editor. The following hippoecm-extension.xml snippet achieves this:

  <sv:node sv:name="myproject-add-featured">
    <sv:property sv:name="jcr:primaryType" sv:type="Name">
      <sv:value>hippo:initializeitem</sv:value>
    </sv:property>
    <sv:property sv:name="hippo:sequence" sv:type="Double">
      <sv:value>10000</sv:value>
    </sv:property>
    <sv:property sv:name="hippo:contentroot" sv:type="String">
      <sv:value>/hippo:configuration/hippo:frontend/cms-preview/workflowPlugin/workflow.categories</sv:value>
    </sv:property>
    <sv:property sv:name="hippo:contentpropadd" sv:type="String">
      <sv:value>featured</sv:value>
    </sv:property>
  </sv:node>