Scenario: Calling Correspondence Management services from within a LiveCycle process

You can call the Correspondence Management solution APIs from within the LiveCycle platform (process/orchestration) as part of a larger workflow.

To build a custom service container, the following libraries are required:
  • com.springsource.org.aopalliance-1.0.0.jar

  • spring-aop-3.0.2.RELEASE.jar

  • spring-asm-3.0.2.RELEASE.jar

  • spring-beans-3.0.2.RELEASE.jar

  • spring-context-3.0.2.RELEASE.jar

  • spring-core-3.0.2.RELEASE.jar

  • spring-expression-3.0.2.RELEASE.jar

  • spring-security-core-3.0.2.RELEASE.jar

  • spring-security-web-3.0.2.RELEASE.jar

  • spring-web-3.0.2.RELEASE.jar

  • dom4j-1.6.1.jar

  • commons-logging-1.1.jar

  • adobe-acm-client.jar

  • adobe-dct-client.jar

  • lc-content-api.jar

Implementation Overview

The examples below create custom service container that either expose (service container operations within your orchestration):
  • Custom service operations that internally use the Correspondence Management APIs.

  • Correspondence Management APIs/operations directly

Custom Service as a service container operation

You can implement a custom service operation that uses the Correspondence Management APIs to implement some functionality, and then expose this operation within your service container container. To do so, perform the following tasks:
  1. Create your custom service

  2. Register the service as a bean

  3. Create the Externalized Property file for the Component

  4. Export the custom service as a LiveCycle Service

  5. Define the service container Component

Create your custom service

Write an interface-based custom service that uses the exposed Correspondence Management APIs to perform the required custom task. The following is an example that exposes a service to retrieve the processed Layout template of a correspondence, and the processed XML data, so that they can be merged to produce the correspondence in a custom format :

The interface, IRenderServiceWrapper.java::

package com.adobe.livecycle.cmsa; 
import java.util.Map; 
import com.adobe.idp.Document; 
public interface IRenderServiceWrapper { 
    public abstract Map<String, Document> getProcessedTemplate(String letterName, String initialXmlData, Boolean useTestData); 
}

The implementation, RenderServiceWrapper.java:

package com.adobe.livecycle.cmsa; 
 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
 
import com.adobe.icc.dbforms.obj.Letter; 
import com.adobe.icc.dbforms.obj.Query; 
import com.adobe.icc.dbforms.obj.Statement; 
import com.adobe.icc.dbforms.obj.Statement.Operator; 
import com.adobe.icc.ddg.api.LetterRenderService; 
import com.adobe.icc.services.api.LetterService; 
import com.adobe.idp.Document; 
 
public class RenderServiceWrapper implements IRenderServiceWrapper { 
 
    private LetterRenderService renderService; 
    private LetterService letterService; 
 
    /** 
     * {@inheritDoc} 
     */ 
    public Map<String, Document> getProcessedTemplate(String letterName, String initialXmlData, Boolean useTestData) 
    { 
        Map<String, Document> result = new HashMap<String, Document>(); 
         
        if (letterName != null && !"".equals(letterName)) 
        {         
            Statement st = new Statement(); 
            st.setAttributeName("name"); 
            st.setOperator(Operator.EQUALS); 
            st.setAttributeValue(letterName); 
 
            Query query = new Query(); 
            query.setObjectType(Letter.class.getSimpleName()); 
            query.addStatement(st); 
            List<Letter> letters = letterService.getAllLetters(query); 
             
            if (letters.size() > 0) 
            { 
             
                Map<String, Object> processedData = renderService.processLetter(letters.get(0).getId(), initialXmlData, useTestData); 
                 
                Document xdp = new Document((byte[])processedData.get(LetterRenderService.LAYOUT_TEMPLATE_KEY)); 
                Document xml = new Document((byte[])processedData.get(LetterRenderService.XML_DATA_KEY)); 
                 
                result.put(LetterRenderService.LAYOUT_TEMPLATE_KEY, xdp); 
                result.put(LetterRenderService.XML_DATA_KEY, xml); 
            } 
            else 
                System.out.println("No Letter found with name : " + letterName); 
                 
        } 
        else 
            System.out.println("No Letter name provided."); 
         
        return result; 
    } 
     
    public LetterRenderService getRenderService() { 
        return renderService; 
    } 
 
    public void setRenderService(LetterRenderService renderService) { 
        this.renderService = renderService; 
    } 
 
    public LetterService getLetterService() { 
        return letterService; 
    } 
 
    public void setLetterService(LetterService letterService) { 
        this.letterService = letterService; 
    } 
}

Register as a Spring bean

Once the above service is implemented, register it as a Spring bean within your (Spring-based) custom service container:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> 
 
 
    <bean name="lc.remotingClientParent" 
        class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean" 
        abstract="true"> 
        <property name="httpInvokerRequestExecutor" ref="CRXAuthHttpInvokerRequestExecutor" /> 
    </bean> 
 
    <bean id="CRXAuthHttpInvokerRequestExecutor" 
        class="com.adobe.livecycle.spring.remoting.CRXAuthHttpInvokerRequestExecutor"> 
        <property name="appRoot" value="/content/apps/cm"/> 
    </bean> 
 
    <bean name="LetterService" parent="lc.remotingClientParent"> 
        <property name="serviceUrl" 
            value="${crx.serverUrl}/bin/remoting/lc.icc.dbservices.letterService" /> 
        <property name="serviceInterface" value="com.adobe.icc.services.api.LetterService" /> 
    </bean> 
     
    <bean name="LetterRenderService" parent="lc.remotingClientParent"> 
        <property name="serviceUrl" 
            value="${crx.serverUrl}/bin/remoting/lc.icc.renderlib.letterRenderService" /> 
        <property name="serviceInterface" value="com.adobe.icc.ddg.api.LetterRenderService" /> 
    </bean> 
 
    <bean name="RenderServiceWrapper" class="com.adobe.livecycle.cmsa.RenderServiceWrapper"> 
        <property name="renderService" ref="LetterRenderService" /> 
        <property name="letterService" ref="LetterService" /> 
    </bean> 
 
    <bean id="testProps" 
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
        <property name="locations"> 
            <list> 
                <value>classpath:cmsa.properties</value> 
                <value>file:C:/Adobe/config/cmsa.properties</value> 
            </list> 
        </property> 
    </bean> 
    
</beans>

Create the Externalized Property file for the Component

The following is an example of the default property file (cmsa.properties) within the component in the classpath:
# URL to the Spring app. 
crx.serverUrl=http://localhost:4502 
 
# more propeties here>

Export the custom service as a LiveCycle service

Now that the services are available as a Spring bean, you can export them as a LiveCycle service. Define a com.adobe.idp.dsc.component.ServiceInstanceFactory implementation (SpringServiceFactory.java) as follows:

package com.adobe.livecycle.spring; 
 
import com.adobe.idp.dsc.DSCException; 
import com.adobe.idp.dsc.component.ComponentContext; 
import com.adobe.idp.dsc.component.support.AbstractServiceInstanceFactory; 
import com.adobe.idp.dsc.registry.infomodel.ServiceConfiguration; 
import org.springframework.beans.factory.BeanFactory; 
import org.springframework.context.access.ContextSingletonBeanFactoryLocator; 
 
public class SpringServiceFactory extends AbstractServiceInstanceFactory { 
    public void activateInstance(ServiceConfiguration serviceConfiguration, 
            Object o) { 
    } 
 
    public Object createInstance(ServiceConfiguration serviceConfiguration) 
            throws DSCException { 
        return getBeanFactory(serviceConfiguration).getBean( 
                serviceConfiguration.getServiceId()); 
    } 
 
    public void destroyInstance(ServiceConfiguration serviceConfiguration, 
            Object o) throws DSCException { 
    } 
 
    public void passivateInstance(ServiceConfiguration serviceConfiguration, 
            Object o) { 
    } 
 
    public boolean validateInstance(ServiceConfiguration serviceConfiguration, 
            Object o) { 
        return true; 
    } 
 
    private BeanFactory getBeanFactory(ServiceConfiguration serviceConfiguration) { 
        String componentId = serviceConfiguration.getComponentId(); 
        ComponentContext ctx = getComponentContext(); 
        ClassLoader cl = Thread.currentThread().getContextClassLoader(); 
        try { 
            Thread.currentThread().setContextClassLoader(ctx.getClassLoader()); 
            return ContextSingletonBeanFactoryLocator.getInstance( 
                    "classpath:/spring/beanRefContext.xml").useBeanFactory( 
                    componentId).getFactory(); 
        } finally { 
            Thread.currentThread().setContextClassLoader(cl); 
        } 
    } 
}

The factory in the example above expects a Spring configuration file named beanRefContext.xml under classpath:/spring, which loads the actual client’s Spring configuration/beans. For example:

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" 
    xmlns:context="http://www.springframework.org/schema/context"> 
 
 
    <!-- 
     Note: The beanId must be same as the componentId 
    --> 
    <bean name="com.adobe.livecycle.cmsa.RenderWrapper" class="org.springframework.context.support.ClassPathXmlApplicationContext"> 
        <property name="configLocations"> 
            <value> 
                classpath:cmsa-spring-config.xml 
            </value> 
            </property> 
    </bean> 
 
</beans>

Follow these conventions to simplify the component/service definitions and expose the beans as a LiveCycle service:

  • The bean ID or name in beanRefContext.xml is the same as the component ID, as defined in the service container's component.xml.

  • The custom service bean has the same name as the LiveCycle service ID, as defined in the service container's component.xml.

Define the service container Component

Next, define the service container component details in its component.xml as follows:

<component xmlns="http://adobe.com/idp/dsc/component/document"> 
    <component-id>com.adobe.livecycle.cmsa.RenderWrapper</component-id> 
    <version>1.0</version> 
    <services> 
        <!-- Service name must be same as the name of the corresponding Spring 
            bean --> 
        <service name="RenderServiceWrapper" orchestrateable="true"> 
            <implementation-class>com.adobe.livecycle.cmsa.IRenderServiceWrapper 
            </implementation-class> 
            <auto-deploy service-id="RenderServiceWrapper" 
                minor-version="0" major-version="1" category-id="Correspondence Management" /> 
            <operations> 
                <operation name="getProcessedTemplate"> 
                    <input-parameter name="letterName" title="Letter Name" 
                        type="java.lang.String" /> 
                    <input-parameter name="initialXmlData" title="Initial XML Data (for DDI)" 
                        type="java.lang.String" /> 
                    <input-parameter name="useTestData" title="Use Letter Test Data?" 
                        type="java.lang.Boolean" /> 
                    <output-parameter name="result" 
                        title="Result Map with Layout and Data" type="java.util.Map" /> 
                </operation> 
            </operations> 
        </service> 
 
                  <!-- Mandatory service to capture CRX credentials --> 
        <service name="CRXUserCredentialService"> 
            <implementation-class>com.adobe.livecycle.crx.CRXUserCredentialService 
            </implementation-class> 
            <factory-method>getInstance</factory-method> 
            <supported-connectors></supported-connectors> 
            <auto-deploy major-version="1" service-id="CRXUserCredentialService" 
                category-id="Correspondence Management" /> 
            <config-parameter name="username" type="java.lang.String" 
                title="Username"> 
                <description>User name with which to connect to CM</description> 
                <default-value>admin</default-value> 
            </config-parameter> 
            <config-parameter name="password" type="java.lang.String" 
                title="Password"> 
                <description>Password for the above user</description> 
                <default-value>admin</default-value> 
                <property-editor editor-id="com.adobe.idp.dsc.propertyeditor.system.PasswordPropertyEditorComponent" /> 
            </config-parameter> 
            <operation-config> 
                <operation-name>*</operation-name> 
                <transaction-type>None</transaction-type> 
            </operation-config> 
        </service> 
    </services> 
    <supports-export>false</supports-export> 
    <class-path>lib/dom4j-1.6.1.jar lib/lc-content-api.jar lib/adobe-acm-client.jar lib/adobe-dct-client.jar lib/com.springsource.org.aopalliance-1.0.0.jar lib/spring-aop-3.0.2.RELEASE.jar lib/spring-asm-3.0.2.RELEASE.jar lib/spring-beans-3.0.2.RELEASE.jar lib/spring-context-3.0.2.RELEASE.jar lib/spring-core-3.0.2.RELEASE.jar lib/spring-expression-3.0.2.RELEASE.jar lib/spring-web-3.0.2.RELEASE.jar lib/spring-security-core-3.0.2.RELEASE.jar</class-path> 
 
    <service-factory-class>com.adobe.livecycle.spring.SpringServiceFactory 
    </service-factory-class> 
</component>

Exposing Correspondence Management services directly as a service container operation

You can also expose the entire Correspondence Management service, that is already exposed over HTTP remoting, and its operations as a LiveCycle service, rather than wrapping it within a custom service. For example, to expose the FormService and its operations, simply add the following entries to the component.xml:

<component xmlns="http://adobe.com/idp/dsc/component/document"> 
  ... 
  ... 
  <services> 
 
    <!-- Again, the Service name/id must be same as the name of the corresponding Spring bean, defined in cmsa-spring-config.xml --> 
 
    <service name="FormService" orchestrateable="true"> 
      <implementation-class>com.adobe.livecycle.cmsa.FormQueryService</implementation-class> 
      <auto-deploy service-id="FormService" minor-version="0" major-version="1" category-id="CMSA"/> 
    </service> 
 
    ... 
    <!-- more services here --> 
    ... 
  </services> 
  ... 
  ... 
</component>

Authentication propagation

You can establish an invocation context for CRX so that the service container configuration can be propagated to call Correspondence Management APIs. Create a custom service configuration that captures a set of CRX user credentials that have permissions over Correspondence Management content. The following definition exposes the configurations on the LiveCycle Doc Services Platform Admin user interface.:

<service name="CRXUserCredentialService"> 
    <implementation-class>com.adobe.livecycle.crx.CRXUserCredentialService 
    </implementation-class> 
    <factory-method>getInstance</factory-method> 
    <supported-connectors></supported-connectors> 
    <auto-deploy major-version="1" service-id="CRXUserCredentialService" 
        category-id="Correspondence Management" /> 
    <config-parameter name="username" type="java.lang.String" 
        title="Username"> 
        <description>User name with which to connect to CM</description> 
        <default-value>admin</default-value> 
    </config-parameter> 
    <config-parameter name="password" type="java.lang.String" 
        title="Password"> 
        <description>Password for the above user</description> 
        <default-value>admin</default-value> 
        <property-editor editor-id="com.adobe.idp.dsc.propertyeditor.system.PasswordPropertyEditorComponent" /> 
    </config-parameter> 
    <operation-config> 
        <operation-name>*</operation-name> 
        <transaction-type>None</transaction-type> 
    </operation-config> 
</service>
To configure:
  1. Navigate to Admin user interface > Services > Applications and Services > Service Management.

  2. Select Correspondence Management as the Category.

  3. Select the CRXUserCredentialService service.

  4. Specify the user name and password, that you created for your CRX instance, that has access to Correspondence Management.

  5. Click Save.

Custom authentication executor

The following CRXAuthHttpInvokerRequestExecutor.java is the custom authentication request executor. This API overrides the Spring's BASIC authentication request executor, and uses the CRXUserCredentialService to establish a system context for API invocations.
package com.adobe.livecycle.spring.remoting; 
 
import java.io.IOException; 
import java.net.HttpURLConnection; 
 
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 
import org.springframework.security.core.context.SecurityContext; 
import org.springframework.security.core.context.SecurityContextHolder; 
import org.springframework.security.remoting.httpinvoker.AuthenticationSimpleHttpInvokerRequestExecutor; 
 
import com.adobe.livecycle.content.appcontext.AppContextManager; 
import com.adobe.livecycle.crx.CRXUserCredentialService; 
 
public class CRXAuthHttpInvokerRequestExecutor extends 
        AuthenticationSimpleHttpInvokerRequestExecutor { 
     
    private String appRoot; 
    public String getAppRoot() { 
        return appRoot; 
    } 
    public void setAppRoot(String appRoot) { 
        this.appRoot = appRoot; 
    } 
    @Override 
    protected void prepareConnection(HttpURLConnection con, int contentLength) 
            throws IOException { 
        boolean clearContext = false; 
        try { 
            if (SecurityContextHolder.getContext().getAuthentication() == null) { 
                clearContext = true; 
                CRXUserCredentialService credentialService = CRXUserCredentialService 
                        .getInstance(); 
                UsernamePasswordAuthenticationToken ut = new UsernamePasswordAuthenticationToken( 
                        credentialService.getUsername(), 
                        credentialService.getPassword()); 
                SecurityContext sctx = SecurityContextHolder 
                        .createEmptyContext(); 
                sctx.setAuthentication(ut); 
                SecurityContextHolder.setContext(sctx); 
            } 
            super.prepareConnection(con, contentLength); 
            con.setRequestProperty(AppContextManager.APP_CONTEXT_HEADER_NAME, appRoot); 
        } finally { 
            if (clearContext) { 
                SecurityContextHolder.clearContext(); 
            } 
        } 
 
    } 
}

// Ethnio survey code removed