
h1. Publication Plugin

h2. Overview

The publication allows ECM user to simply create and configure different lifecycle for documents. Each lifecycle will be described by a plugin, and the publication service will be in charge to store plugins and dispatch request on a node to the good plugin.

At first, we will browse the differents functions of the interface of the service. Next, we will describe functions of the interface of the plugins. Finallly, we will see how to create and configure a new plugin.

h2. Description of the service interface :
{code}
/*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.ecm.publication;

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

import javax.jcr.Node;

import org.exoplatform.services.ecm.publication.plugins.PublicationPlugin;

/**
 * Created by The eXo Platform SAS
 * Author : Romain Denarie
 *          romain.denarie@exoplatform.com
 * 7 mai 08  
 */
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
   */ 
  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
   * @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).
   */
  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).
   */
  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
   * @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).
   */
  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).
   */
  public String[][] getLog(Node node) throws NotInPublicationLifecycleException, 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).
   */
  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
   */
  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).
   */
  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).
   */
  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
   */
  public void enrollNodeInLifecycle(Node node, String lifecycle) throws AlreadyInPublicationLifecycleException, 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
   * @result a string localized and where values are substitute
   */
  public String getLocalizedAndSubstituteLog(Locale locale, String key, String[] values);

}
{code}

Each lifecycle is stored in the service. When a request is raised for a node, it is correctly send to the appropriate lifecycle.

The correct use of this service is :
When user selects publication for a node, the function "enrollNodeInLifecycle" is called. Then, the node is associated to the lifecycle.
There is a TODO in the ECM view. When more than one lifecycle is available, we should allow user to choose which one he want to use. Actually, when user click on publication button, node is associated to the only one existing lifecycle : StaticAndDirectPublicationPlugin.

# second service exists for the publication : it is PublicationPresentationService.
This service is dependant of UIForm and is in charge to get the panel to display in the portal. For getting the panel, the service asks to the plugin. When a plugin is add to the PublicationService, it is automatically adsded to the PublicationPresentationService.

{code}
 /*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.ecm.publication;

import javax.jcr.Node;

import org.exoplatform.services.ecm.publication.plugins.PublicationPlugin;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.form.UIForm;

/**
 * Created by The eXo Platform SAS
 * Author : Romain D�nari�
 *          romain.denarie@exoplatform.com
 * 7 mai 08  
 */
public interface PublicationPresentationService {
 
  /**
   * Retrieves the WebUI form corresponding to the current 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 state UI should be retrieved
   * @return a WebUI form corresponding to the current state and 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).
   */
  public UIForm getStateUI(Node node, UIComponent component) throws NotInPublicationLifecycleException, Exception;
  
  /**
   * Add a Publication Plugin to the service.
   * The method caches all added plugins.
   * 
   * @param p the plugin to add
   */
  public void addPublicationPlugin(PublicationPlugin p);
  
}
{code}

h2. Description of the plugin interface :

{code}
/*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.ecm.publication.plugins;

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

import javax.jcr.Node;

import org.exoplatform.container.component.BaseComponentPlugin;
import org.exoplatform.services.ecm.publication.IncorrectStateUpdateLifecycleException;
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
   */
  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
   * @return a WebUI form corresponding to the current state and node.
   */
  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
   * @return an array of bytes corresponding to the image to be shown to the
   * user
   */
  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
   * @return a String giving the current state.
   */
  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
   * 
   * @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
   */
  public abstract boolean canAddMixin (Node node) throws Exception;

  /**
   * Add the specific plugin mixin to the node
   * @param node
   */
  public abstract void addMixin (Node node) throws Exception;
}
{code}

All of theses functions should be implemented to describe the functionment of the lifecycle.
Each plugin can be develop in a jar, to easily add or remove one from the portal.

3. How to create a new plugin 
Create a new package : org.exoplatform.services.ecm.publication.pluginName
In this package, you must create a class which implement interface PublicationPlugin : PluginNamePlugin.java
Other classes of this package describe the specific part of the UI panel.


!panelUI.jpg|height=400,width=574,width=bottom,width=center!

For staticAndDirectPublicationPlugin, there is two states, and each one is represented by a form : UINonPublishedForm and UIPublishedForm. WIth this we display differents things in one state or in another.
The function getUIState in the plugin create a new instance of the panel and return it for displyed in the portal. The panel must contain form item to be able to change the state of the node, for example, for staticAndDirectPublication, the panel allows to change the "visibility" or the state of the selected version.

To configure the service : 
in configuration.xml, there is : 

{code}
<component>
    <key>org.exoplatform.services.ecm.publication.PublicationService</key>
    <type>org.exoplatform.services.ecm.publication.impl.PublicationServiceImpl</type>
    <component-plugins>
    	<component-plugin>
		    <name>StaticAndDirect</name>
		    <set-method>addPublicationPlugin</set-method>
		    <type>org.exoplatform.services.ecm.publication.plugins.staticdirect.StaticAndDirectPublicationPlugin</type>
		    <init-params>
		      <values-param>
		    		<name>StaticAndDirect</name>
		    		<description>This publication lifecycle keeps the content at their original place. Any version of the Node can be published.</description>
		      </values-param>
		    </init-params>
	  </component-plugin>
    </component-plugins>
  </component>

  <component>
    <key>org.exoplatform.services.ecm.publication.PublicationPresentationService</key>
    <type>org.exoplatform.services.ecm.publication.impl.PublicationPresentationServiceImpl</type>
  </component>
{code}

Theses lines launch the two services (PublicationService and PublicationPresentationService) and add automatically the plugin StaticAndDirectPublication.
 