Publication service

Background

The previous publication model in eXo ECM is based on moves of contents from specific JCR locations to other ones. More precisely, content is dropped by the user to a given location. This has the effect to start a workflow instance. After the workflow has received the approvals from appropriate users, it moves the content to a live location and then to a backup location. However this current situation has several limitations:

  • The workflow is at the centre of the publication process. So implementing alternate publication flavours is made difficult. In addition, monitor and control is tedious,
  • The publication states of contents is represented by the possible JCR locations: "Validation request", "Pending", "Published", "Backed up", "Trash". It is not possible to adapt easily this set,
  • In some situations, it is preferable to have the content to stay at the initial location instead of being moved,
  • We don't have a centralized UI approach in JCR File Explorer to control publication.
This document specifies how the Publication will be improved in eXo ECM. In particular, it is driven by the needs of the CG95 customer project. When the implementation is over, we will be virtually able to implement any publication lifecycle requested by customer projects.

Design

Introduction

At a high level, a new "Publication" service will be created. This service will be invoked by all entities that need to retrieve or specify publication information. For example : User interfaces, workflow instances, ECM actions. It will accept plugins that specify a publication lifecycle model. By default, eXo ECM will provide two types of plugins :

  • MoveAndWorkflowPublicationPlugin. A Workflow instance is triggered and will contribute to liven up the lifecycle. The contents are moved from one JCR location to another one.
  • StaticAndDirectPublicationPlugin. The contents are directly pushed in publication by the user action. They stay at the original location. The requirements of this plugin are those of the CG95 project.
When a customer project comes with non-standard publication lifecycle requirements, it will be easy for us to create a new plugin. Finally, a new mixin type will be created in all cases to hold common publication information. Each plugin will be able to extend it and add its own specific properties.

Publication service

This service will be added to the main ECM component project.

When eXo is initialized, publication plugins will be added to the service. They will define lifecycles, which modelize states, transitions and actions. The addPublicationPlugin() method is used towards this goal. As for the method getPublicationPlugins(), it lists all plugins which have been registered so far. That way, it is possible to retrieve the names of all possible lifecycles. The idea is that each JCR node first needs to be enrolled in a publication lifecycle in order to be published, thanks to enrollNodeInLifecycle(). In its parameters, this method expects a name of a lifecycle, which corresponds to a publication plugin name. The isNodeEnrolledInLifecycle() checks whether a specified node has been added in a lifecycle.

When a node is enrolled, a mixin type is added to it. It specifies the current state in the lifecycle. The method changeState() allows to update the current state of the specified node. It will delegate to the plugin of the used lifecycle. An atypical point in that service is that it allows generating a WebUI form corresponding to a state in the lifecyle. This is done through the getStateUI() method. The main intent for that is to enable the JCR File Explorer to display a specific panel and allow the user specifying information for the current state. The returned WebUI form will possibly show fields where user can specify information and push buttons.

Finally, there exists the getUserInfo(), getStateImage() and getLog() methods to respectively retrieve textual description, visual description and history information. The snippet below defines its interface. The provided Javadoc needs to be added to the source code when it is implemented.

package org.exoplatform.services.ecm.publication;

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

import javax.jcr.Node;

public interface PublicationService {

  /**
   * Add a Publication Plugin to the service.
   * The method caches all added plugins.
   * 
   * @param p the plugin to add
   */
  public void addPublicationPlugin(PublicationPlugin p);

  /**
   * Retrieves all added publication plugins.
   * This method is notably used to enumerate possible lifecycles.
   * 
   * @return the added publication plugins
   */
  public Map<String,PublicationPlugin> getPublicationPlugins();


  /**
   * Update the state of the specified node.
   * This method first inspects the publication mixin bound to the specified
   * Node. From that mixin, it retrieves the lifecycle registered with the
   * node. Finally, it delegates the call to the method with same name in the
   * plugin that implements the lifecycle.
   * 
   * @param node the Node whose state needs to be changed
   * @param newState the new state.
   * @param context a Hashmap containing contextual information needed
   * to change the state. The information set is defined on a State basis.
   * A typical example is information submitted by the user in a user
   * interface.
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws IncorrectStateUpdateLifecycleException if the update is not
   * allowed
   * @throws Exception the exception
   */ 
  public void changeState(Node node, String newState, HashMap<String, String> context) 
  throws NotInPublicationLifecycleException, IncorrectStateUpdateLifecycleException, Exception;

  /**
   * Retrieves an image showing the lifecycle state of the specified Node.
   * The method first inspects the specified Node. If it does not contain
   * a publication mixin, then it throws a NotInPublicationLifecycleException
   * exception. Else, it retrieves the lifecycle name from the mixin,
   * selects the appropriate publication plugin and delegates the call to it.
   * 
   * @param node the node from which the image should be obtained
   * @param locale the locale
   * 
   * @return an array of bytes corresponding to the image to be shown to the
   * user
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws Exception the exception
   */
  public byte[] getStateImage(Node node,Locale locale)throws NotInPublicationLifecycleException ,Exception;

  /**
   * Retrieves the name of the publication state corresponding to the
   * specified Node.
   * This method first inspects the specified Node. If it does not contain
   * a publication mixin, then it throws a NotInPublicationLifecycleException
   * exception. Else, it retrieves the current state name from the mixin.
   * Possible examples of State names are : "draft", "validation requested",
   * "publication pending", "published", "backed up", "validation refused".
   * 
   * @param node the node from which the publication state should be retrieved
   * 
   * @return a String giving the current state.
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws Exception the exception
   */
  public String getCurrentState(Node node) throws NotInPublicationLifecycleException ,Exception;

  /**
   * Retrieves description information explaining to the user the current
   * 
   * This method first inspects the specified Node. If it does not contain
   * a publication mixin, then it throws a NotInPublicationLifecycleException
   * exception. Else, it retrieves the lifecycle name from the mixin,
   * selects the appropriate publication plugin and delegates the call to it.
   * 
   * @param node the Node from which user information should be retrieved
   * @param locale the locale
   * 
   * @return a text message describing the state of the current message.
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws Exception the exception
   */
  public String getUserInfo(Node node, Locale locale) throws NotInPublicationLifecycleException ,Exception;
  
  /**
   * Retrieves the history of publication changes made to the specified Node.
   * 
   * This method first inspects the specified Node. If it does not contain
   * a publication mixin, then it throws a NotInPublicationLifecycleException
   * exception. Else, it retrieves the lifecycle name from the mixin,
   * selects the appropriate publication plugin and delegates the call to it.
   * 
   * Log entries are specified as a multi-valued property of the publication
   * mixin.
   * 
   * @param node the Node from which the history Log should be retrieved
   * 
   * @return a String array with 2 dimensions. The first dimension contains
   * each log entry. The second dimension contains each information in a log
   * entry, which are : date, name of the new state, involved user, additional
   * information.
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws Exception the exception
   */
  public String[][] getLog(Node node) throws NotInPublicationLifecycleException, Exception; 
  
  /**
   * Adds the log.
   * 
   * @param node the node
   * @param log the log
   * 
   * @throws NotInPublicationLifecycleException the not in publication lifecycle exception
   * @throws Exception the exception
   */

  
  /**
   * Adds a log entry to the specified Node.
   * The specified array of String defines the Log information to be added.
   * Log entries are specified as a multi-valued property of the publication
   * mixin.
   * 
   * @param node the Node from which the history Log should be updated
   * @param log the Log information to be added
   * log contains : date, newState, userInvolved, key for additionalInformation in locale with possible subsitutions, values for substitutions
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws Exception the exception
   */
  public void addLog(Node node, String[] log) throws NotInPublicationLifecycleException, Exception; 
  
  /**
   * Determines whether the specified Node has been enrolled into a
   * lifecycle.
   * 
   * @param node the Node from which the enrollment should be evaluated
   * 
   * @return true of the Node is enrolled
   * 
   * @throws Exception the exception
   */
  public boolean isNodeEnrolledInLifecycle(Node node) throws Exception;

  /**
   * Retrieves the name of the lifecycle in which the specified Node has
   * been enrolled.
   * 
   * @param node the Node from which the enrollment should be retrieved
   * 
   * @return the name of the lifecycle corresponding to the specified Node
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws Exception the exception
   */
  public String getNodeLifecycleName(Node node) throws NotInPublicationLifecycleException, Exception;

  /**
   * Retrieves the description of the lifecycle in which the specified Node
   * has been enrolled.
   * 
   * This method first inspects the specified Node. If it does not contain
   * a publication mixin, then it throws a NotInPublicationLifecycleException
   * exception. Else, it retrieves the lifecycle name from the mixin,
   * selects the appropriate publication plugin and delegates the call to it.
   * 
   * @param node the Node from which the enrollment should be retrieved
   * 
   * @return the description of the lifecycle corresponding to the specified
   * Node
   * 
   * @throws NotInPublicationLifecycleException in case the Node has not
   * been registered in any lifecycle yet (in other words, if no publication
   * mixin has been found).
   * @throws Exception the exception
   */
  public String getNodeLifecycleDesc(Node node) throws NotInPublicationLifecycleException ,Exception;

  /**
   * Enroll the specified Node to the specified lifecycle.
   * This method adds a publication mixin to the specified Node. The lifecycle
   * name is the one specified as parameter. By default, the state is set
   * to "enrolled".
   * 
   * @param node the Node to be enrolled in the specified lifecycle
   * @param lifecycle the name of the lifecycle in which the Node should be
   * enrolled
   * 
   * @throws AlreadyInPublicationLifecycleException the already in publication lifecycle exception
   * @throws Exception the exception
   */
  public void enrollNodeInLifecycle(Node node, String lifecycle) throws AlreadyInPublicationLifecycleException, Exception;
  
  /**
   * Unsubcribe node that in publication lifecyle.
   * 
   * @param node the node
   * 
   * @throws NotInPublicationLifecycleException the not in publication lifecycle exception
   * @throws Exception the exception
   */
  public void unsubcribeLifecycle(Node node) throws NotInPublicationLifecycleException, Exception;  
  
  /**
   * Get localized log messages and substitute variables.
   * 
   * @param locale : the locale to use
   * @param key : the key to translate
   * @param values : array of string to susbtitute in the string
   * 
   * @return the localized and substitute log
   * 
   * @result a string localized and where values are substitute
   */  
  public String getLocalizedAndSubstituteLog(Locale locale, String key, String[] values);
  
  /**
   * Gets the localized and substitute log for current node. 
   * Base on lifecycle that node is enroll. The method call to get message for each lifecycle
   * 
   * @param node the node
   * @param locale the locale
   * @param key the key
   * @param values the values
   * 
   * @return the localized and substitute log
   * 
   * @throws NotInPublicationLifecycleException the not in publication lifecycle exception
   * @throws Exception the exception
   */
  public String getLocalizedAndSubstituteLog(Node node, Locale locale, String key, String[] values) throws NotInPublicationLifecycleException, Exception;
}

Plublication mixin type

A new "exo:publication" mixin type is used to decorate nodes enrolled in a publication lifecycle. It is defined as follows:

PropertyMeaning
exo:lifecycleNameName of the lifecycle.
exo:currentStateName of the current state in the lifecycle. The default value is "enrolled".
exo:historyLogAn array of Strings. Each String is a log entry. It contains comma "," separated tokens. The tokens are the log information themselves (date, name of the new state, involved user, additional information).

Please node that each property of this mixin type has an "onParentVersion" flag set to "IGNORE". This is to avoid taking into account publication information when making versions.

Publication plugins

As said earlier, the Publication Service is enriched by many publication plugins. They will implement the lifecycle of the publication model. They will be identified by a name. This name will be specified as a property of the publication mixin. That way, the Publication Service will easily retrieve which publication plugin should be used when delegating a call.

Each plugin is packaged in its own jar file. The jar file contains an XML configuration file that registers the plugin to the Publication Service (using external configuration). Apart from the class of the plugin itself, the jar also contains :

  • the possible WebUI classes to define publication state forms including corresponding action listeners.
  • images representing the state to the user (typically a graph highlighting the current state).
  • resource bundles to specifiy user information to describe the current state.
The fact that plugins are contained and defined in separate jar files will make it easy to add new lifecycles.

The most significant method of the plugins is changeState(), which applies the lifecycle and modifies the state of a Node.

The snippet below defines its interface. The provided Javadoc needs to be added to the source code when it is implemented.

package org.exoplatform.services.ecm.publication;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import javax.jcr.Node;

import org.exoplatform.container.component.BaseComponentPlugin;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.form.UIForm;

/**
 * Base class of Publication plugins.
 * Publication plugins implement a publication lifecycle. Each time a new
 * custom lifecycle needs to be defined, a new plugin has to be implemented
 * and registered with the Publication Service.
 * 
 * The getName() method in the parent class is used to identify the lifecycle.
 * The getDescription() method in the parent class is used to describe the
 * lifecycle. Internationalization resource bundles are used in the
 * implementation of the method.
 */
public abstract class PublicationPlugin extends BaseComponentPlugin {

  /**
   * Retrieves all possible states in the publication lifecycle.
   * 
   * @return an array of Strings giving the names of all possible states
   */
  public abstract String[] getPossibleStates();

  /**
   * Change the state of the specified Node.
   * The implementation of this method basically retrieves the current
   * state from the publication mixin of the specified Node. Then, based on
   * the newState, it is able to determine if the update is possible. If
   * yes, appropriate action is made (eg: launch a publication workflow). In
   * all cases, the current state information is updated in the publication
   * mixin of the specified Node.
   * 
   * @param node the Node whose state needs to be changed
   * @param newState the new state.
   * @param context a Hashmap containing contextual information needed
   * to change the state. The information set is defined on a State basis.
   * 
   * @throws IncorrectStateUpdateLifecycleException if the update is not
   * allowed
   * @throws Exception the exception
   */
  public abstract void changeState(Node node,
      String newState,
      HashMap<String, String> context)
  throws IncorrectStateUpdateLifecycleException, Exception;

  /**
   * Retrieves the WebUI form corresponding to the current state of the
   * specified node.
   * There are two cases here. Either the form contains read only fields (when
   * the state is supposed to be processed by an external entity such as a
   * Workflow). Or the form has editable fields or buttons (in the case the
   * user can interfere. In that case, some action listeners are leveraged.).
   * In all cases, all UI and listener classes are provided in the JAR
   * corresponding to the PublicationPlugin.
   * The method first inspects the specified Node. If it does not contain
   * a publication mixin, then it throws a NotInPublicationLifecycleException
   * exception. Else, it retrieves the lifecycle name from the mixin,
   * selects the appropriate publication plugin and delegates the call to it.
   * 
   * @param node the Node from which the state UI should be retrieved
   * @param component the component
   * 
   * @return a WebUI form corresponding to the current state and node.
   * 
   * @throws Exception the exception
   */
  public abstract UIForm getStateUI(Node node, UIComponent component) throws Exception;

  /**
   * Retrieves an image showing the lifecycle state of the specified Node.
   * The implementation of this method typically retrieves the current state
   * of the specified Node, then fetches the bytes of an appropriate image
   * found in the jar of the plugin. This image is supposed to be shown in
   * the publication dialog of the JCR File Explorer Portlet.
   * 
   * @param node the node from which the image should be obtained
   * @param locale the locale
   * 
   * @return an array of bytes corresponding to the image to be shown to the
   * user
   * 
   * @throws IOException Signals that an I/O exception has occurred.
   * @throws FileNotFoundException the file not found exception
   * @throws Exception the exception
   */
  public abstract byte[] getStateImage(Node node, Locale locale) throws IOException,FileNotFoundException,Exception;

  /**
   * Retrieves description information explaining to the user the current
   * publication state of the specified Node. Possible examples are
   * - "The document has been submitted to the following group for validation:
   * /organization/management.".
   * - "The document has been validated and will be published from
   * May 3rd 10:00am to May 3rd 10:00pm. At that time, it will be unpublished
   * and put in a backup state.".
   * - "The document is in draft state. At any time you can turn it to
   * published state."
   * 
   * The returned message should be obtained from internationalization
   * resource bundles (ie not hardcoded).
   * 
   * @param node the node from which the publication state should be retrieved
   * @param locale the locale
   * 
   * @return a String giving the current state.
   * 
   * @throws Exception the exception
   */
  public abstract String getUserInfo(Node node, Locale locale) throws Exception;

  /**
   * Retrieves the lifecycleName.
   * 
   * @return a String giving the lifecycleName
   */

  public String getLifecycleName() {
    return getName();
  }

  /**
   * Retrieves the description of the plugin.
   * 
   * @param node the node
   * 
   * @return a String giving the description
   */
  public String getNodeLifecycleDesc(Node node) {
    return getDescription();
  }

  /**
   * Return if the plugin can add the specific mixin for the publication.
   * 
   * @param node the node to add the mixin
   * 
   * @return boolean
   * 
   * @throws Exception the exception
   */
  public abstract boolean canAddMixin (Node node) throws Exception;

  /**
   * Add the specific plugin mixin to the node.
   * 
   * @param node the node
   * 
   * @throws Exception the exception
   */
  public abstract void addMixin (Node node) throws Exception;
  
  /**
   * Retrieves a node view of the specific node in a context
   * 
   * @param node the node
   * @param context the context
   * 
   * @return the node to view
   * 
   * @throws Exception the exception
   */
  public abstract Node getNodeView(Node node, Map<String,Object> context) throws Exception;
  
  /**
   * Get localized log messages and substitute variables.
   * 
   * @param locale : the locale to use
   * @param key : the key to translate
   * @param values : array of string to susbtitute in the string
   * 
   * @return the localized and substitute log
   * 
   * @result a string localized and where values are substitute
   */
  public abstract String getLocalizedAndSubstituteMessage(Locale locale, String key, String[] values) throws Exception;   
}

By default, eXo will come with two out-of-box publication plugins. It will be possible to define new plugins if they do not fit the customer project needs.

MoveAndWorkflowPublicationPlugin

The lifecycle behind this publication plugin is the one existing is eXo ECM so far. A document is first dropped to the "/Documents/validation requests" JCR folder, where an ECM action triggers a validation workflow. This workflow has a validation task assigned to a specific group in the organization. When a relevant user activates this task, he can either validate the document, refuse the document (the user needs to change the document), refuse the request (the validation request is rejected) or delegate to another user. If he accepts the document, then he has to specify start and end publication dates. Before the first date is met, the document is moved to "/Documents/pending". Then it is moved to "/Documents/live". Finally, when the end publication date is met, the document is moved to a backup directory.

Here is how this lifecycle is implemented and translated into the new Publication Plugin mechanism :

MethodSpecification
getName()Returns "MoveAndWorkflow"
getDescription()Returns "This publication lifecycle triggers a validation workflow and moves the content to appropriate locations of the JCR. Each location corresponds to a specific state."
getPossibleStates()["enrolled", "validation requested", "publication pending", "publication refused", "publication rejected", "published", "backed up"]
Please node that the delegation state is not embodied. In fact, the publication state will still remain "publication pending" in this situation. A log entry will be added to the history log, to keep track of the delegation.
changeState()* If the current state is "enrolled" and the new state is "validation requested", then update the mixin and triggers a validation workflow. This situation typically arises when a PublicationAction is triggered in the validation requests folder. Update the state information in the mixin. Append a log entry.
* If the current state is "validation requested" and the new state is "publication pending", then moves the document from the validation request folder to the pending folder. Update the state information in the mixin. Append a log entry.
* If the current state is "validation requested" and the new state is "publication rejected", then do nothing. Update the state information in the mixin. Append a log entry.
* If the current state is "validation requested" and the new state is "publication rejected", then move the document to the trash folder. Update the state information in the mixin. Append a log entry.
* If the current state is "publication pending" and the new state is "published", then move the document to the live folder. Update the state information in the mixin. Append a log entry.
* If the current state is "published" and the new state is "backed up", then move the document to the back up folder. Update the state information in the mixin. Append a log entry.

In all other cases, throw an IncorrectStateUpdateLifecycleException to indicate that the state update is not possible.
getStateUI()For now, return a blank form (we will possibly improve this in the future).
getStateImage()Return an image showing the possible states in boxes, with possible connections between them. Highlight the box corresponding to the current state.
getUserInfo()* If the current state is "enrolled", return "This document has been enrolled into this lifecycle. To trigger a validation request, it needs to be manually dropped to the [SPECIFY DYNAMICALLY THE VALIDATION REQUEST FOLDER LOCATION] folder."
* If the current state is "validation requested", return "The document has been requested for validation by user [SPECIFY DYNAMICALLY THE USER NAME]. Users of group [SPECIFY DYNAMICALLY THE VALIDATOR GROUP NAME] are now validating the document."
* If the current state is "publication pending", return "The document publication has been accepted. It will be published from [SPECIFY DYNAMICALLY THE START PUBLICATION DATE] to [SPECIFY DYNAMICALLY THE END PUBLICATION DATE].
* If the current state is "publication refused", return "The publication has been refused. The document needs to be revised. The originator [SPECIFY DYNAMICALLY THE ORIGINATOR NAME] should change the content, open the Workflow Portlet and activate the task corresponding to his request.
* If the current state is "publication rejected", return "The publication has been rejected by user [SPECIFY DYNAMICALLY THE USER]. The document has been moved to [SPECIFY DYNAMICALLLY THE TRASH LOCATION]. A new document should be created and a new validation request be made.
* If the current state is "published", return "The document is published and located at [DYNAMICALLY SPECIFY THE LIVE FOLDER]. The publication will end at [DYNAMICALLY SPECIFY THE END PUBLICATION DATE]".
* If the current state is "backed up", return "The document is no more published and is in back up state. It is located at [DYNAMICALLYT SPECIFY THE BACKUP FOLDER].

The plugin parameters define the following information:

  • JCR locations of validation requests, pending documents, live documents, trashed documents and backed up documents. This means that the validation workflow will no more contain this information. It will delegate all moves to the publication service.
  • Organization groups of validators.
  • Name of the validation workflow to trigger.
The mixin type "exo:moveWithWorkflowPublication" inherits from "exo:publication". It adds the following fields:

PropertyMeaning
exo:startPublicationDateThe date at which the content Node should be published
exo:endPublicationDateThe date at which the content Node should not be published anymore
exo:originatorUserNameThe name of the user who requested the publication

Please node that each property of this mixin type has an "onParentVersion" flag set to "IGNORE". This is to avoid taking into account publication information when making versions.

The workflow action listeners should be rewritten. From now, instead of directly orchestrating and updating documents, they will delegate to the publication service.

StaticAndDirectPublicationPlugin

This lifecycle implements the publication flavour chosen by the CG95. In this model, the user directly publishes content. This means that there is no validation. In addition, the content Nodes stay at their original location.

The model presents 3 states only: "enrolled", "non published", "published". The different versions of the Node have different publication states. This means that any version of a Node can be made active and published. Only one version can be in "published" state at a time. The "enrolled" state is specified to comply with the publication model. The "non published" state is on when no version of the Node is published. The "published" state is on when one version of the Node is published. There will be additional properties to the mixin type to specify the publication state of each version.

In addition to the publication state, this lifecycle has a visibility flag, which allows to specify whether a published content can be seen by guest users (ie "public" content) or by private users who can access a Portal. In both case, the plugin defines JCR permissions accordingly.

Here is how the lifecycle is implemented:

MethodSpecification
getName()Returns "StaticAndDirect"
getDescription()Returns "This publication lifecycle keeps the content at their original place. Any version of the Node can be published."
getPossibleStates()[enrolled, non published, published]
changeState()* If the new state is "enrolled", directly switch the state to "draft" (this means that there will never be nodes in "enrolled" states).
* If the current state is "draft" and the new state is "published", the context parameter should contain the node version UUID to be published and the visibility level. If not, throw an IncorrectStateUpdateLifecycleException.
Extract the current state of the version Node to publish. If that Node has no state (not present in the list yet) or is in "Draft" state, make the version Node to be published as "Published" in the "exo:versionsPublicationStates". If not, throw an IncorrectStateUpdateLifecycleException exception. Extract the "visibility" information from the context and update accordingly the "exo:visibility" mixin property.
If the visibility is "public", modify the access permission so that the "any" user can at least view the content. If the visibility is "private", make sure that no "any" permission entry is set.
To finish, make the version to be published as the active version. Append a log entry (should specify the version).
* Same if the current state is "published" and a new state is "published". This situation arises when a version Node is already published and another Version node needs to be published. In addition, extract a possible previously published Node version from the "exo:versionsPublicationStates" mixin property. Make that version Node state "Backed up".
If the current state is "published" and the new state is "unpublished", extract the current published version Node from the "exo:versionsPublicationStates" and make this version as "Backed up". Append a log entry (should specify the version).

In all other cases, throw an IncorrectStateUpdateLifecycleException to indicate that the state update is not possible.
getStateUI()* If the current state is "enrolled" or "non published", display a text stating "No version is currently published. Please select a version to be published". Display a version tree of the current node (including the current version. This tree is the same as the one shown in the versions JCR File Explorer dialog). Allow the user to select one Node. A selector will be labelled "Visibility:" and will have two options "public" and "public". A "Publish" button will be at the bottom. When that button is pressed, invoke changeState() by specifying "published" as newState and the selected version Node plus the selected visibility in the context.
* If the current state is "published", display a text stating "The following version is published: [version id] - [version label].". Ideally, a hypertext link should allow the user to view the document when he clicks it. An "Unpublish" button will allow to unpublish the Node. In that case, the changeState() method is invoked with newState set to "unpublished".
Apart from that, the WebUI Form is the same as the one in the previous case.
getStateImage()
Return an image with two entities : "unpublished" and "published". The "published" entity has multiple boxes, to show that multiple versions can be published. The two entities are connected by a two-arrows link. Depending on the current state, one entity or the other is highlighted.
getUserInfo()* If the current state is "enrolled" or "non published", return "This document is not currently published".
* If the current state is "published", return "The version [version_id] (labelled [version_label]) is currently published. Its visibility is [visibility_level]. This means that [all/restricted] users can view the content."

The mixin type "exo:staticAndDirectPublication" inherits from "exo:publication". It adds the following fields:

PropertyMeaning
exo:visibilityDefines whether the content is visible to all users ("public") or to a specific set of users who can access a Portal ("private"). In this last case, JCR permissions should be set accordingly.
exo:versionsPublicationStatesThe type of this property is an array of String. Each string is a coma separated entry, containing in first position a version node UUID and in second position a version publication state, that is : [UUID],[State]. The possible values for the version states are "draft", "published", "backed up".

JCR File Explorer Dialog

A new icon will be made available to the JCR File Explorer toolbar. When the user hits this icon, a new dialog appears. This dialog contains information related to the publication state and lifecycle bound to the current node.

If the current node has not been enrolled in any lifecycle yet (ie no publication mixin has been added), then a popup message appears. It contains the text: "The current node has not been enrolled to any publication lifecycle yet. Do you want to enrol it in a lifecycle?". A selector lists the available lifecycles names. If the user selects "yes", then the Publication Service is called to enrol appropriately the Node. The publication dialog is immediately displayed after that.

The dialog has two tabs. The first tab is entitled "State". The second tab is entitled "History".

In the "State" tab, the user will see the following things:

  • Name of the node,
  • Name of the publication lifecycle in which the Node is enrolled,
  • Name of the current state. That name should be in the locale of the current user, this means that there should be internationalization resource bundles to translate the state identifier into a specific language. To do so, resource bundles keys are used like this : publication.[plugin name].[state name],
  • Image illustrating the current state,
  • Specific WebUI Form to manipulate the current state.
In the "History" tab, the user will see a table listing all history log entries.

ECM Publication action

A new generic publication action will be created. This one will be called "ChangePublicationState". It will change the publication state of a content Node and will allow the following parameters:

  • lifecycleName : specifies the name of publication lifecycle,
  • newState : specifies the name of the new state,
  • context : specifies optional information to be used when invoking the changeState() method in the publication plugin.
Basically, the "lifecycleName" parameter is only used in case the newState parameter is set to "enrolled". In that particular case, the action will enrol the Node into the specified lifecycle. In all other cases, the used lifecycle is obtained from the mixin bound to the Node and the changeState() method in the publication plugin is called.

Possible use cases for this action are:

  • Each document in a specific JCR location should be enrolled with a lifecycle. In that case, a publication action is set in "add mode" into that location. It will be configured with newState set to "enrol". The lifecycleName parameter will contain the lifecycle in which the Node should be enrolled.
  • The user needs to put a document into a specified JCR location to publish it. In that case, a publication action is set in "add mode" into that location. It will be configured with the newState to "published".
  • The user needs to publish a document by right clicking on it in JCR File Explorer. In that case, a publication action is set in "read mode" on that document. It will change the current state to "published" for example.

Creator: Brice Revenant on 2008/10/30 12:34
Copyright (c) 2000-2009. Allright reserved - eXo platform SAS
1.6.13286