Invoking LiveCycle services from Custom Actions

LiveCycle Content Services (deprecated) provides you with the ability to develop custom actions. A custom action can be used to invoke a LiveCycle service or process. This section focuses on the task of invoking a LiveCycle service from a custom action. (See Creating Custom Actions.)

Remarque : Adobe is migrating Adobe® LiveCycle® Content Services ES customers to the Content Repository built on the modern, modular CRX architecture, acquired during the Adobe acquisition of Day Software. The Content Repository is provided with LiveCycle Foundation and is available as of the LiveCycle ES4 release.

To understand how custom actions can be used to invoke a LiveCycle service, consider the following scenario, which will be used as the basis for all code examples in this section.

Assume that you want to create a custom action that invokes a review and approval process created in LiveCycle Workbench.

The content model requires that you define a reviewer aspect that is available in the Content Services (deprecated) content model so that it can be applied by using the custom action in the repository.

a LiveCycle invocation class is needed to encapsulate the business logic to invoke the process from the custom action.

After you create the logic , you must package and deploy your custom action to Content Services (deprecated).

The review and approval example discussed in this section shows how to create a custom action that invokes the LiveCycle process, and is based on a sample located within your SDK installation at \sdk\misc\ContentServices\adobe-contentservices-src.zip\process-samples.

Start Advanced Process vs. Invoke a Process

This chapter dicusses how to create an "Advanced Process" to be invoked through the Content Space. It is important to understand the difference between "Invoke a LiveCycle Process" available from the "Run Action" menu and the "Start Advanced Process" available from the "Details" menu.

The "Invoke a LiveCycle Process" can be used with any process that has one input variable of type document. Any other combination of inputs will cause the process to not be listed in the process drop-down box. When you choose the "Invoke a LiveCycle Process" action, the selected the node(document) in the Content Space will be used as an input to the selected process. The "Advanced Process", discussed in this chapter, is useful in situations where multiple inputs are required for the process to be invoked. The user is prompted to enter values with a wizard-like interface. Once all the inputs have been filled, the process is invoked. The "Advanced Process" is useful for invoking long-lived processes and complex workflows.

Summary of steps

To develop a custom action, perform the following steps:

  1. Set up your development environment.

  2. Define your application logic.

  3. Define the user interface resources.

  4. Package and deploy the component.

Setting up your development environment

To create a custom action, you must set the Eclipse Compiler Compliance Level to Java SE Development Kit version 1.5 (5.0) or later, and then you must import the JAR files located in \lib\server folder located in the SDK zip file, including the following JAR files:

  • alfresco-repository.jar

  • alfresco-core.jar

  • alfresco-web-client.jar

  • alfresco-webscript-framework.jar

    You must also include the following JAR files in the build path:

  • myfaces-api-1.1.5.jar located in the \dependencies\ folder

  • spring-2.0.8.jar located in the \dependencies\ folder

  • jta.jar located in the \dependencies\ folder

  • servlet.jar located in the \dependencies\devenv\ folder

  • adobe-usermanager-client.jar

  • adobe-livecycle-client.jar

    Finally, you must also include all the JAR files within \sdk\misc\ContentServices\adobe-contentservices-src.zip\ContentServices\lib\

Defining your application logic

Creating a custom model

To define a custom model, in some cases, create a custom model to contain the new aspect and tags to be assigned by the user. In this example, the model uses a prefix of lcProcess, the aspect is called lcProcess:reviewer, and the property is called lcProcess:reviewDescription.

This example contains a sample implementation of the review and approval custom model and is defined in the liveCycleProcessModel.xml file.

<?xml version="1.0" encoding="UTF-8"?> 
<!-- Definition of new Model --> 
<model name="lcProcess:model" xmlns="http://www.alfresco.org/model/dictionary/1.0"> 
 
    <!--Optional metadata about the model --> 
    <description>LiveCycle Process Model</description> 
    <author>LiveCycle Content Service ES</author> 
    <version>1.0</version> 
     
    <!--Imports are required to allow references to definitions in other models--> 
    <imports> 
        <!--Import Alfresco Dictionary Definitions --> 
        <import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" /> 
        <!--Import Alfresco Content Domain Model Definitions --> 
        <import uri="http://www.alfresco.org/model/content/1.0" prefix="cm" /> 
    </imports> 
 
    <!--Introduction of new namespaces defined by this model --> 
    <namespaces> 
        <namespace uri="http://www.adobe.com/lc/process/1.0" prefix="lcProcess"/> 
    </namespaces> 
     
    <types> 
        <type name="lcProcess:reviewAndApproval"> 
            <parent>cm:content</parent> 
                <properties> 
                    <!--  Task Description --> 
                    <property name="lcProcess:reviewDescription"> 
                        <title>Review Description</title> 
                        <type>d:text</type> 
                    </property> 
                </properties> 
                <mandatory-aspects> 
                    <aspect>lcProcess:reviewer</aspect> 
                </mandatory-aspects> 
        </type> 
    </types> 
    <aspects> 
        <!--                       --> 
           <!--     Task Reviewer     --> 
           <!--                       -->         
           <aspect name="lcProcess:reviewer"> 
               <associations> 
         <association name="lcProcess:reviewer"> 
                    <title>Reviewer</title>                 
                       <source> 
                           <mandatory>false</mandatory> 
                           <many>false</many> 
                       </source> 
                       <target> 
                           <class>cm:person</class> 
                           <mandatory>true</mandatory> 
                           <many>false</many> 
                       </target> 
                   </association> 
               </associations> 
           </aspect> 
       </aspects> 
</model>

Implementing the action executor class

The custom action consists of an ActionExecutor class and its associated bean declaration. An action must implement the org.alfresco.repo.action.executor.ActionExecuter interface. The ActionExecuter interface is best implemented by extending the abstract class ActionExecuterAbstractBase, which provides basic services for action executor implementations.

Your implementation of the ActionExecuterAbstractBase interface must include an addParameterDefinitions method, which is used to add parameters to the custom repository action, as well as an executeImpl method, which contains the actual business logic.

The name of the action executor is defined by the static NAME attribute, which is assigned the value of "process-action" as shown in the following code example.

public class ReviewProcessActionExecuter extends ActionExecuterAbstractBase 
{ 
    // process name - this will be used to refer to the action. 
    public static final String NAME = "process-action";

Adding action parameters

To implement the addParameterDefinitions method, go into its definition and create a new ParameterDefinition object, as shown in the following code example. This example shows how to use the parameter list to add a new parameter definition. In this example, PARAM_REVIEWER_ID, PARAM_ASSIGNER_ID, and PARAM_REVIEW_DESC contain the names of the parameters.

// action parameters 
// reviewer name 
public static final String PARAM_REVIEWER_ID = "reviewer-id"; 
// assigner name 
public static final String PARAM_ASSIGNER_ID = "assigner-id"; 
// review description 
public static final String PARAM_REVIEW_DESC = "review-desc"; 
 
protected void addParameterDefinitions(List<ParameterDefinition> paramList) { 
        paramList.add(new ParameterDefinitionImpl(PARAM_REVIEWER_ID, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_REVIEWER_ID))); 
        paramList.add(new ParameterDefinitionImpl(PARAM_ASSIGNER_ID, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_ASSIGNER_ID))); 
        paramList.add(new ParameterDefinitionImpl(PARAM_REVIEW_DESC, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_REVIEW_DESC)));  
}

Implementing the custom action

The executeImpl method is where the custom action business logic resides. It receives an Action object and the NodeRef of the node on which the action is performed. In this example, the executeImpl method adds the reviewer aspect to the node and uses the list to set the node’s reviewDescription property.

Because the executeImpl method uses NodeService, PersonService, AuthenticationService, and UserManagerUtils, you must inject those services by using Spring dependency injection, as shown in the following code example.

The following example shows how to inject services.

    private NodeService nodeService; 
    private AuthenticationService authenticationService; 
    private PersonService personService; 
    private UserManagerUtils userManagerUtils = null; 
 
    // Inject Node Service. 
    public void setNodeService(NodeService nodeService)  
    { 
        this.nodeService = nodeService; 
    } 
 
    // Inject Person Service. 
    public void setPersonService(PersonService personService)  
    { 
        this.personService = personService; 
    } 
 
    // Inject Authentication Service. 
    public void setAuthenticationService(AuthenticationService aAuthenticationService) 
    { 
             this.authenticationService = aAuthenticationService; 
    } 
 
    // Inject UserManagerUtils 
    public void setUserManagerUtils(UserManagerUtils aUserManagerUtils) 
    { 
        this.userManagerUtils = aUserManagerUtils; 
    }

In this case, the custom action invokes a LiveCycle process, so the ReviewProcessActionExecuter.executeImpl method contains logic to obtain the user context, create a service client factory, and invoke the LiveCycle process. To obtain the user context, invoke the AuthenticationService.getCurrentTicket method to obtain a ticket, and pass that ticket to the UserManagerUtils.getIDPContext method. To create the service client factory, pass the user context to the ServiceClientFactory.createInstance method, as shown in the following example.

Obtaining the user context

This example shows how to obtain the user context and create a service client. For information about creating service clients, see Invoking a service using a Java client library.

String ticket = authenticationService.getCurrentTicket(); 
Context idpContext = userManagerUtils.getIDPContext(ticket); 
ServiceClientFactory _scFactory = ServiceClientFactory.createInstance(idpContext);

For this example, the class defining the methods in which LiveCycle services are invoked is called LiveCycleServiceInvoker. The ReviewProcessActionExecuter.executeImpl method invokes the process by using the methods in the LiveCycleServiceInvoker class, as shown in the following example.

Invoking the LiveCycle process

This example contains the logic that is contained within the ReviewProcessActionExecuter.executeImpl method to create an instance of the LiveCycleInvoker class, set its client factory, and use the class to invoke a LiveCycle process.

LiveCycleServiceInvoker _lcServiceInvoker = new LiveCycleServiceInvoker(); 
_lcServiceInvoker.setServiceClientFactory(_scFactory); 
try { 
    _lcServiceInvoker.invokeLongLivedOrchestration(NAME_REVIEW_AND_APPROVAL_PROCESS, _processParamMap); 
} catch (DSCException e) { 
    e.printStackTrace(); 
    throw new ProcessExecutionException(e.getMessage()); 
}

Defining the ReviewProcessActionExecuter implementation

This example shows the entire implementation of the ReviewProcessActionExecuter class, in which the business logic is contained in the executeImp method, and the parameters are added through the addParameterDefinitions method.

package com.adobe.livecycle.samples.cs.action; 
 
import java.util.ArrayList; 
import java.util.List; 
 
 
import org.alfresco.repo.action.ParameterDefinitionImpl; 
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; 
import org.alfresco.service.cmr.action.Action; 
import org.alfresco.service.cmr.action.ParameterDefinition; 
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; 
import org.alfresco.service.cmr.repository.NodeRef; 
import org.alfresco.service.cmr.repository.NodeService; 
import org.alfresco.service.cmr.security.AuthenticationService; 
import org.alfresco.service.cmr.security.PersonService; 
import org.alfresco.service.namespace.QName; 
 
import com.adobe.contentservices.authentication.UserManagerUtils; 
import com.adobe.contentservices.common.ContentServicesConstants.CSContentModel; 
import com.adobe.contentservices.logging.CSModulesLogger; 
import com.adobe.idp.Context; 
import com.adobe.idp.dsc.DSCException; 
import com.adobe.idp.dsc.clientsdk.ServiceClientFactory; 
import com.adobe.livecycle.samples.cs.util.LiveCycleServiceInvoker; 
 
 
 
/** 
    * This is an Action Executer class to define an action (invocable from Java Script) that 
    * can be used to initiate a Review and Approval process. It can be used to execute a Review 
    * and Approval action from the Office Plug-in. 
    *  
    * @author Akhil Kumar Jain 
    */ 
public class ReviewProcessActionExecuter extends ActionExecuterAbstractBase  
{ 
    // specify logger 
    private static final CSModulesLogger s_csLogger = CSModulesLogger.getCSModuleLogger(ReviewProcessActionExecuter.class);  
     
    // process name - this will be used to refer to the action. 
    public static final String NAME = "process-action"; 
     
    // action parameters 
    // reviewer name 
    public static final String PARAM_REVIEWER_ID = "reviewer-id"; 
    // assigner name 
    public static final String PARAM_ASSIGNER_ID = "assigner-id"; 
    // review description 
    public static final String PARAM_REVIEW_DESC = "review-desc"; 
     
    // LiveCycle service that will be invoked for this action. 
    private static final String NAME_REVIEW_AND_APPROVAL_PROCESS    = "Samples - Content Services (deprecated) - ReviewAndApproval"; 
 
    // parameters for the "Samples - Content Services (deprecated) - ReviewAndApproval" service 
    private static final String PARAMETER_IN_REVIEWER_OID           = "inReviewerOID"; 
    private static final String PARMATER_IN_REVIEW_DESCRIPTION      = "inReviewDescription"; 
    private static final String PARAMETER_IN_REVIEW_INITIATOR_OID   = "inReviewInitiatorOID"; 
    private static final String PARAMETER_IN_STORE_NAME             = "inStoreName"; 
    private static final String PARAMETER_IN_REVIEW_CONTENT_URLLIST = "inReviewContentURLList"; 
 
     
    private NodeService nodeService; 
    private AuthenticationService authenticationService; 
    private PersonService personService; 
    private UserManagerUtils userManagerUtils = null; 
 
    // Inject Node Service. 
    public void setNodeService(NodeService nodeService)  
    { 
        this.nodeService = nodeService; 
    } 
 
    // Inject Person Service. 
    public void setPersonService(PersonService personService)  
    { 
        this.personService = personService; 
    } 
 
    // Inject Authentication Service. 
    public void setAuthenticationService(AuthenticationService aAuthenticationService) 
    { 
         this.authenticationService = aAuthenticationService; 
    } 
 
    // Inject UserManagerUtils 
    public void setUserManagerUtils(UserManagerUtils aUserManagerUtils) 
    { 
        this.userManagerUtils = aUserManagerUtils; 
    } 
 
     
    /** 
     * This method is used to specify the parameters for the action. 
     * @param The list that should be filled with all the parameters. 
     */ 
    protected void addParameterDefinitions(List<ParameterDefinition> paramList)  
    { 
        paramList.add(new ParameterDefinitionImpl(PARAM_REVIEWER_ID, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_REVIEWER_ID))); 
        paramList.add(new ParameterDefinitionImpl(PARAM_ASSIGNER_ID, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_ASSIGNER_ID))); 
        paramList.add(new ParameterDefinitionImpl(PARAM_REVIEW_DESC, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_REVIEW_DESC))); 
    } 
 
    /** 
     * This method is used to define the flow when the action defined by this ActionExecuter is 
     * executed. 
     * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef) 
     */ 
    protected void executeImpl( 
            Action ruleAction, 
            NodeRef actionedUponNodeRef)  
    { 
 
        s_csLogger.info("Invoking action ==> " + ReviewProcessActionExecuter.NAME, ""); 
         
        if (this.nodeService.exists(actionedUponNodeRef) == true) 
        { 
 
            // Get the parameter values 
            String assigner = (String)ruleAction.getParameterValue(PARAM_ASSIGNER_ID); 
            String reviewer = (String)ruleAction.getParameterValue(PARAM_REVIEWER_ID); 
            String reviewDesc = (String)ruleAction.getParameterValue(PARAM_REVIEW_DESC); 
 
            s_csLogger.info("Fetching action parameters ..." , ""); 
             
             
            // Step: Collect the process parameters 
            java.util.Map<String, Object> _processParamMap = new java.util.HashMap<String, Object>(); 
     
            // Get the Review Initiator OID 
            String _reviewInitiatorOID = getUserOID(assigner);         
            s_csLogger.debug("ReviewerInitiatorName=" + assigner + ",OID=" + _reviewInitiatorOID, ""); 
            _processParamMap.put(PARAMETER_IN_REVIEW_INITIATOR_OID, _reviewInitiatorOID); 
     
            // Get the Review Description         
            s_csLogger.debug("ReviewDescription=" + reviewDesc); 
            _processParamMap.put(PARMATER_IN_REVIEW_DESCRIPTION, reviewDesc); 
     
            // Get the Reviewer OID 
            s_csLogger.debug("ReviewerName=" + reviewer + ",OID=" + getUserOID(reviewer), "");             
            _processParamMap.put(PARAMETER_IN_REVIEWER_OID, getUserOID(reviewer)); 
     
            String nodeName = actionedUponNodeRef.toString(); 
     
            // Get the Store name 
            // Items are of the form: workspace://SpacesStore/bb090e72-bf94-11dc-af8e-7d7d65ccd02d 
            String[] _itemWebURLSplit = nodeName.split("//"); 
            String _itemURL = _itemWebURLSplit[1]; 
            String[] _itemURLSplit = _itemURL.split("/"); 
            _processParamMap.put(PARAMETER_IN_STORE_NAME, _itemURLSplit[0]); 
     
            // Construct Review Content URL List 
            java.util.List<String> _reviewContentURLList = new ArrayList<String>(); 
            int _idx = nodeName.lastIndexOf("/"); 
            if (_idx == -1 || ((_idx+1) == nodeName.length())) 
            { 
                throw new IllegalArgumentException("Illegal URL [" + nodeName + "]"); 
            } 
            _reviewContentURLList.add(nodeName.substring(_idx+1)); 
     
            s_csLogger.debug("Content URL List=" + _reviewContentURLList, ""); 
            _processParamMap.put(PARAMETER_IN_REVIEW_CONTENT_URLLIST, _reviewContentURLList); 
     
            // Step: Invoke "Samples - Content Services (deprecated) - ReviewAndApproval" service          
            // Get the User Context  
            String ticket = authenticationService.getCurrentTicket(); 
            Context idpContext = userManagerUtils.getIDPContext(ticket); 
            ServiceClientFactory _scFactory = ServiceClientFactory.createInstance(idpContext); 
            s_csLogger.info("Invoking Service " + NAME_REVIEW_AND_APPROVAL_PROCESS, ""); 
 
            // Invoke the process with the given parameters 
            LiveCycleServiceInvoker _lcServiceInvoker = new LiveCycleServiceInvoker(); 
            _lcServiceInvoker.setServiceClientFactory(_scFactory); 
            try { 
                _lcServiceInvoker.invokeLongLivedOrchestration(NAME_REVIEW_AND_APPROVAL_PROCESS, _processParamMap); 
            } catch (DSCException e) { 
                e.printStackTrace(); 
                throw new ProcessExecutionException(e.getMessage()); 
            } 
 
            s_csLogger.info("Process: '" + NAME_REVIEW_AND_APPROVAL_PROCESS + "' launch COMPLETED.", ""); 
             
        } 
    } 
 
    /** Gets the OID for the given user */ 
    private String getUserOID(String userName) 
    { 
        NodeRef _personNode = personService.getPerson(userName); 
        return (String)nodeService.getProperty(_personNode, QName.createQName(CSContentModel.PROP_OID)); 
    } 
 
} 

Implementing the invoker class

To invoke a LiveCycle service, use the service client factory to create an invocation request and pass that request to the service client’s invoke method. (See Invoking a service using a Java client library.)

Defining the LiveCycle service invocation class

This example shows the implementation of the LiveCycleServiceInvoker class that contains the application logic for creating a service client and invoking the service or process, which is used by the ReviewProcessActionExecuter.executeImpl method. The primary logic in which the invocation request is created is in the invokeOperation method shown in this example.

/** 
    * ADOBE SYSTEMS INCORPORATED 
    * Copyright 2008 Adobe Systems Incorporated 
    * All Rights Reserved 
    *  
    * NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the 
    * terms of the Adobe license agreement accompanying it.  If you have received this file from a 
    * source other than Adobe, then your use, modification, or distribution of it requires the prior 
    * written permission of Adobe. 
    */ 
package com.adobe.livecycle.samples.cs.util; 
 
 
import com.adobe.contentservices.logging.CSModulesLogger; 
import com.adobe.idp.dsc.DSCException; 
import com.adobe.idp.dsc.InvocationRequest; 
import com.adobe.idp.dsc.InvocationResponse; 
import com.adobe.idp.dsc.clientsdk.ServiceClientFactory; 
 
/** 
    * LiveCycle Service Invoker 
    *   
    * @author Rohit Kumar (rohitk@adobe.com) 
    * @since 24 December 2007 
    */ 
public class LiveCycleServiceInvoker 
{ 
       private static final CSModulesLogger s_logger = CSModulesLogger.getCSModuleLogger(LiveCycleServiceInvoker.class); 
       private static final String ORCHESTRATION_OPERATION_NAME = "invoke"; 
     
       private ServiceClientFactory m_serviceClientFactory = null; 
     
       /** Setter the Service Client Factory */ 
       public void setServiceClientFactory(ServiceClientFactory aServiceClientFactory) 
       { 
           m_serviceClientFactory = aServiceClientFactory; 
       } 
     
       /** Invokes a Long Lived Orchestration with the give parameters */ 
       public void invokeLongLivedOrchestration(String serviceName, java.util.Map<String, Object> paramMap) 
       throws DSCException 
       { 
           s_logger.debug("Invoking Long Lived Orchestration [" + serviceName + "]"); 
           s_logger.debug("Service Parameters=" + paramMap); 
     
           invokeOperation(serviceName, ORCHESTRATION_OPERATION_NAME, paramMap, false); 
       } 
 
       /** Invokes a Short Lived Orchestration with the give parameters */ 
       public java.util.Map invokeShortLivedOrchestration(String serviceName, java.util.Map<String, Object> paramMap) 
       throws DSCException 
       { 
           return invokeOperation(serviceName, ORCHESTRATION_OPERATION_NAME, paramMap, true); 
       } 
 
       /** Executes aServiceName.aOperationName(operationParams) */ 
       public java.util.Map invokeOperation(String aServiceName, String aOperationName,  
               java.util.Map<String, Object> operationParamMap, boolean isSynchronousInvocation) 
       throws DSCException  
       { 
           InvocationRequest _request = m_serviceClientFactory.createInvocationRequest( 
                   aServiceName, aOperationName, operationParamMap, isSynchronousInvocation); 
 
           InvocationResponse _response = m_serviceClientFactory.getServiceClient().invoke(_request); 
           return _response.getOutputParameters();             
       } 
} 

Defining the user interface resources

The user interface resources may be defined through the action and parameter messages, the action JavaServer page (JSP), an action handler, a Spring configuration file that is used to register the action as a bean, and a configuration file. (See Creating Custom Actions.)

Packaging and deploying the custom action component

To deploy a custom action, see Creating Custom Actions.