Monday, May 18, 2015

Content Management Lifecycles in Nuxeo

Lifecycle Versus Workflow

Content lifecycle refers to the states or phases that content transitions through over time. A lifecycle can be visually represented using UML or a state transition diagram.  Content states act as identifiers that can be used by search.  They're also often used to control content access permissions.  Additionally, the change of content state can cause actions to be triggered.

Content lifecycles shouldn't be confused with workflows.  Workflows are used to manage the processes that occur within and between lifecycles.  They model the flow of tasks associated with a process.  Very often content will be attached to a workflow and acted on as part of the completion of the tasks that make up the workflow.

It's a best practice to cleanly separate lifecycle and workflow when implementing content management.  Workflow typically is the thing that enables content to move through the different states of its lifecycle.  But because lifecycles and workflows work closely together, it's easy to focus on building workflows without explicitly modeling the states of the lifecycle.

Very often lifecycle is something that is implicitly modeled in content management systems.  In that case, metadata schemas or content models will be extended to include a state variable to track the state of the content.  Workflows are then built to update state information as content moves through a process.

Nuxeo Content Lifecycle

In Nuxeo the concept of content lifecycles comes prewired as part of the Studio web-based configuration tool.  Within Studio you can define the states in the lifecycle and the transition paths between them.

Here is a screenshot from Studio of the default lifecycle state transition diagram.  For the default lifecycle, possible states include project, obsolete, deleted and approved.

Using Studio, let's now define a new lifecycle called ReviewCycle with states called Draft, Review, Reject, Available, Obsolete as follows:

This new lifecycle can then be associated with a new document type definition called ReviewType using Studio configuration forms.  All ReviewType documents on creation will automatically be associated with this new lifecycle definition.  Within Studio, in the definition of the document type, we reference the ReviewCycle Lifecycle as follows:

In this case our new document type is defined as inheriting from the standard File type.  We'll make no other changes, other than to define a different label and icons associated with the new document type.

To briefly see how our new document type and lifecycle with states work, we can create a simple demonstration using an Automation Chain that transitions a ReviewType document that is in the initial state Draft to the state Review.  Note that the name of the transition between these two states is called to_Review.

Next let's create a button in the UI that the user can press that we can program to change the state of the document.  To do that we create a User Action as follows:

Here we've uploaded a new icon of a right-pointing arrow that will be visible as a button to click on that will then run our Automation Chain.  Note that we specify that the button should be visible only when a document is in the lifecycle state of Draft.  At the bottom of the definition form we specify the Automation Chain that the button is associated with: setReviewLifeCycle.

Now let's run our test.  We first create our new document type called ReviewType (label is Review):

On the summary page for the document we can see that the state of the document is initially Draft.  Next to the permanent link icon on the document Title header we can see a new icon that we've added that when clicked on will automatically transition the state of the lifecycle to Review.

When we click on this transition icon, the summary page refreshes and the document is then in the Review state.  Note that the transition icon button is no longer displayed in the Title.

One more thing we can demonstrate is how easy it is to then programatically locate all documents that are in a specific lifecycle state.  We can power up the Nuxeo shell and run an NXQL query against the repository to find all documents that are in the Review state.  In our case, it will be just the one we uploaded and transitioned.

We can see that in the next screenshot where we issue an NXQL query.  The query finds the document (called Application) that we've just uploaded and transitioned into the Review state.

Select * from Document where ecm:currentLifeCycleState = 'Review'.

Saturday, May 9, 2015

Nuxeo Automation Scripting with Nashorn

In March, Nuxeo Platform Fast Track 7.2 was released.  One new feature of that release is Automation Scripting enabled by Java 8 and Nashorn.

Nashorn replaces the Rhino Javascript scripting engine in Java 8.  Nashorn is based on JSR 262 and provides better compliance with the ECMA normalized Javascript specification.  Compared to Rhino, the performance and memory usage of Nashorn is significantly better.

Thierry Delprat wrote a blog that introduces Nuxeo Automation Scripting with Nashorn.

I wanted to test out the new feature, so I thought that I'd apply scripting to a "drop folder" use case.  In a "drop folder"scenario, an action is triggered that processes and then files documents as they are created and dropped into a folder.

This is typically how imports from a capture product like Ephesoft are handled.  For example, in the Ephesoft case, scanned images are written to a repository folder using CMIS, and then a rule or action associated with the folder pushed to from Ephesoft further processes document metadata and ultimately files the document into a target folder.

The Drop Folder Scenario

I wanted to see how easy it would be to use Nuxeo Automation Scripting to create a "drop folder" script.

For this test scenario, I created two Workspaces in Nuxeo called "Drop Folder" and "Target Folder".

Under Target Folder, I created the following three folders:

The idea for the test script is that documents will be uploaded into the "Drop Folder".  Based on the mimetype of the document, it will be moved into the "Target Folder" area and filed under "PDF Files" if the document is PDF, under "Word Files" if the mimetype is Word, or otherwise filed under "Other Files".

In addition, the description field for the document will be updated with information stating that the document was autofiled and the time the file was made.


To implement this, in Nuxeo Studio, I first created an Automation Script called "OnImportScript" and an Event Handler called "OnDocImport".

The event handler tracks the "Document created" events and is triggered when items are created in the folder "/default-domain/workspaces/Drop Folder".  The operation that is run when the event is triggered is called OnImportScript.

Here is the screen for configuring the event handler.

Next I filled in the Javascript code that runs when the event triggers.  The Javascript code for the OnImportScript is shown below.  In that code, the run() method will be called to start the processing.

The run() method identifies the location of Target Folder and moves the document based on the mimetype.

var WORD_MIMETYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
var PDF_MIMETYPE = "application/pdf";

var WORD_FOLDER = "Word Files";
var PDF_FOLDER = "PDF Files";
var OTHER_FOLDER = "Other Files";
var TOP_TARGET_FOLDER = "Target Folder";

//  Get the parent node for the current node
function getParentFolder(node)
    // Get the parent reference -- org.nuxeo.ecm.core.api.DocumentRef
  var parent = node.getParentRef();
  return Repository.GetDocument(node, { 'value': parent.toString() });  

// Return a child of a folder by name
function getChildByName(fldr, childName)
  if(! fldr.getDocumentType().isFolder() ) return null;
  var children = Document.GetChildren(fldr, {});

  if(children==null) return null;
  for each(var child in children)
      if(child.getName().equals(childName))  return child;

  return null;

function getFolderChild(fldr, childName)
  var child = getChildByName(fldr, childName);
  if(child)  return child;
  // Create the child folder if it doesn't exist
  return Document.Create(fldr, { 'type': 'Folder', 'name': childName });


//  ctx --  java.util.HashMap
//  input -- org.nuxeo.ecm.core.api.impl.DocumentModelImpl
//  params --  java.util.HashMap
function run(ctx, input, params) {
  // Event is instance of org.nuxeo.ecm.core.event.impl.EventImpl
  // If the event isn't "Create Document", then return
   if(!ctx.Event.getName().equals("documentCreated")) return;
  //  If the input isn't a document, then return
  if( input===null || !input.getClass().getName().equals("org.nuxeo.ecm.core.api.impl.DocumentModelImpl") ) return;
  // If the input is a folder and not a document, ignore it
  if( input.getDocumentType().isFolder() ) return null;
  // Collect some information about the document
  var doc = input;
  var docBlob = Document.GetBlob(doc, {});
  var mimeType = docBlob.getMimeType();
  var dropFolderNode   = getParentFolder(doc);
  var dropFolderParent = getParentFolder(dropFolderNode);
  var targetFolder =  getFolderChild(dropFolderParent, TOP_TARGET_FOLDER); 
  // File the incoming document based on its mimetype
  var targetFolderSub;
      targetFolderSub = getFolderChild(targetFolder, PDF_FOLDER);
  else if(mimeType.equals(WORD_MIMETYPE))
      targetFolderSub = getFolderChild(targetFolder, WORD_FOLDER);
     targetFolderSub = getFolderChild(targetFolder, OTHER_FOLDER);
  //  File the document by moving it to the target folder
  if(targetFolderSub != null)
     // Move the document to the correct target folder
     Document.Move(doc, { 'target': targetFolderSub.getId()} );
     var now = new Date();
     Document.SetProperty(doc,{"xpath":'dc:description', "save":true, "value":"Autofiled from folder '" + dropFolderNode.getName() + "' at " + now.toLocaleString()  });



To test, I created documents in the Drop Folder and saw that the Document created event successfully triggered and filed the documents to the correct target folders based on the mimetype.

I was happy with the results, although my testing was minimal.  The purpose of this was just to see how Nuxeo Automation Scripting works and to validate that the approach could be used for creating Javascript-based folder rules, although the approach isn't limited to just putting rules on folders.

Wednesday, November 26, 2014

Deploying Nuxeo IDE Customizations

In this article, I have a tip for deploying Nuxeo customizations developed  using the Nuxeo Eclipse IDE.

But first let me first mention a few things about how to go about customizing Nuxeo.

There are two main methods for customizing the Nuxeo web application:
  • Nuxeo Studio
  • Nuxeo Eclipse IDE plugin

Nuxeo Studio

Nuxeo Studio is a cloud-based configuration tool.  It's available by subscription, but it's not required to have in order to use Nuxeo.  Actually, without Studio you don't miss out on any of the cool end-user Nuxeo product features, but by using it, you can save yourself a tremendous amount of development and administration time, and you'll also know that your configurations made in Studio are guaranteed to be automatically upgradable to future product versions of Nuxeo.  

Nuxeo without Studio is much harder.  If you don't use Studio you'll need to hand-code and debug quite a few configuration files.  That can involve writing a lot of XML, XHTML and other code, easily hundreds of lines of code even for simple configurations.  And while manually creating those files isn't really that complex, writing those files can be tedious, and it's easy to introduce syntax errors while writing them that may end up later costing you many hours of time trying to track down and fix. 

You can do a lot with Studio, and if you're serious about Nuxeo, you really should use it.  We've found, for example, that most projects we work on start out by developing a custom content model and designing associated create, view and edit forms.  With Nuxeo Studio, an analyst could easily build and test the content model and all associated forms without needing assistance from a developer.  Studio also allows you to graphically design workflows, set up automation tasks, and a lot more.

Nuxeo IDE

For many installations, using Nuxeo Studio for configuring your application is sufficient, but if you need to do even more in-depth customizations than what Studio lets you do, you can use Nuxeo's Eclipse plugin.  Nuxeo has great on-line documentation showing you how to use it.  Unlike Studio, the Nuxeo IDE is a tool that targets Java developers.

Using the Nuxeo Eclipse IDE you can extend and override parts of the Nuxeo application.  From a Nuxeo perspective within Eclipse you can create a Nuxeo project and then add artifacts to it.  You can then deploy your project changes, launch Tomcat and run and debug the Nuxeo application, all within the Eclipse environment.

Deploying the Project Bundle

Now for the tip.  

It's easy to hot reload Nuxeo projects within Eclipse using the Nuxeo IDE plugin.  That feature really speeds up development.  But when deploying your IDE-developed customizations to a new Nuxeo instance, there's an additional deployment file that you need to have in your project.

Nuxeo has an option in the IDE to jar all the files of your project.  To deploy your changes, you just create the jar and then drop it into the nxserver/bundles directory of your new instance and restart.

The option to jar is available by first right-clicking on your project in the Eclipse Nuxeo perspective and then selecting Nuxeo and Export Jar.

That's easy enough.  But there's one more thing you need to do to prepare the jar file for deployment on another server, and if you don't do it, you're likely to run into problems.  This step isn't needed when you're developing and deploying from within Eclipse and is easy to overlook when reading Nuxeo's explanation for how to use the Nuxeo Eclipse IDE.

Every time Tomcat is restarted, the nuxeo.war directory under nxserver will get redeployed and expanded.  Because of that, any files you may have attempted to manually add to the nuxeo.war area after a deployment will be lost the next time the war is redeployed.  

A feature of the Nuxeo Eclipse IDE is that after the war is expanded, the project's web asset files from the src/main/resources/web directory of your project will be automatically copied into the war area, modifying the standard Nuxeo instance with your customizations.

But when deploying your project jar file to another Nuxeo instance, if you just copy over the jar to the new instance, the web asset files in your jar won't be visible to Tomcat.  Similar to what is done automatically for you with the Eclipse hot reload, the web asset files need to be placed within the expanded war.  This can be done using the deployment-fragment.xml file.  This file needs to be placed in your project at the top of the directory src/main/resources/OSGI-INF, for example:

Here's an example of what you can put in that file:

<?xml version="1.0"?>
<fragment version="1">
  <extension target="application#MODULE"> 
    <delete path="${bundle.fileName}.tmp"/>
    <unzip from="${bundle.fileName}" to="${bundle.fileName}.tmp"/>
    <copy from="${bundle.fileName}.tmp/web/nuxeo.war" to="/"/>
    <append from="${bundle.fileName}.tmp/OSGI-INF/I18n/" to="nuxeo.war/WEB-INF/classes/" addNewLine="true"/>

You can see that the install section of the code unjars your bundle and copies over all assets that are under the web directory of your project to the corresponding area of the expanded nuxeo.war.

That's it.  With the deployment-fragment.xml file in place, your bundle will be correctly deployed into the target Nuxeo instance when Tomcat starts up.

The content of the deployment-fragment.xml file looks something like an ant build file.  It describes tasks that are run when the bundle file is loaded.  Some of the things that you can script in this file include:

    • unzip or unjar files 
    • create folders 
    • move files
    • delete files and folders
    • append files

[Note that there was a problem in the initial release of Nuxeo 6.0 for handling hot reloads.  Future releases are fixed.  For the 6.0 release, this JIRA explains a workaround.]