Avg. Rating 5.0

Problem

While working on a LiveCycle project with multiple services, you may require the addition of an aspect/interceptor to all your services.

Solution

The Common Building Block (part of the Adobe LiveCycle Solution Accelerator program) comes with several scripts and utility classes which allow an interceptor to be added to a DSC. This can be integrated into your existing LiveCycle application build script as this cookbook shows

Detailed explanation

Pre-Requisites

These scripts have been tested to work with Apache Ant version 1.7.1. You also need to install Ant Contrib 1.0b3 as well as Jakarta BSF 2.4.0 and Groovy 1.5.7 into the Ant lib folder. This cookbook works with Adobe LiveCycle ES1u1 (8.2)

Sample

In this example we have a simple service in our DSC which has a single method: sum()

package com.example.dsc.sample;
public interface SimpleCalculatorService {
    public Double sum(Double num1, Double num2);
}

This interface is implemented by an impementation class:

package com.example.dsc.sample.impl;
import com.example.dsc.sample.SimpleCalculatorService;

public class SimpleCalculatorServiceImpl implements
SimpleCalculatorService {
    public Double sum(Double num1, Double num2) {
        return num1 + num2;
    }
}

For any service to be used with the Common Building Block, it must have a service interface as above. Also note the package of the implementation class ( impl); we will use this later. Download the Common Building Block from the Adobe Prerelease site here. Extract the building block into your DSC project. 

  While working on a LiveCycle project with multiple services, you may require the addition of an aspect/interceptor to all your services.

Copy facade.properties and tools-build.properties from cmn_1_0\dist\build-tools into the same folder as your existing ant build script. These files are used to configure the code generation scripts inside of CMN. The tools-build.properties, is annotated with the expected values for various properties.

The scripts work by parsing the project's component.xml and generating facade classes based on the operations listed inside of that. Specify the values as below:

component.xml=src/component.xml

gen.facade.out.dir=gen-facade
facade.custom.exception=
facade.interceptor=com.example.dsc.sample.interceptor.SimpleInterceptor
facade.custom.imports=
facade.properties.file=facade.properties
facade.impl.package=impl
gen.facade.header=

Note: There are multiple utility scripts inside of CMN. Since we are using only the facade generation, we need not specify values for the rest of the properties.

This notifies CMN that facade classes are to be placed inside a source folder known as gen-facade, that the implementation classes for each service interface are present in an package known as impl and that the facade should call an interceptor class known as SimpleInterceptor.

Not all the files inside the component.xml need to be intercepted - you can specify the list of services to be intercepted using facade.properties file. This file contains a single line - listing the Facade class and the Service for which that Facade exists.

com.example.dsc.sample.facade.SimpleCalculatorFacade=SimpleCalculatorService

Next we need to integrate the scripts into our build script. Add the following to your build script's ant.properties:

common.dist.dir=${basedir}/sa/building_blocks/cmn_1_0/dist
lcsdk=C:/Adobe/LiveCycleES2/LiveCycle_ES_SDK
lcsdk.clientlib=${lcsdk}/client-libs
lcsdk.clientlib.tp=${lcsdk.clientlib}/thirdparty
lcsdk.clientlib.common=${lcsdk.clientlib}/common

After this, import the tools-build.xml from CMN into your build script:

    <property file="ant.properties"/>
    <import
file="${common.dist.dir}/build-tools/tools-build.xml"/>

We now get several new targets available in our build:

While working on a LiveCycle project with multiple services, you may require the addition of an aspect/interceptor to all your services.

We need to map the service classes in the component.xml to the generated Facade classes.

<service name="SimpleCalculatorService" orchestrateable="true"
title="SimpleCalculatorService">
    <specification-id>
        com.example.dsc.sample.SimpleCalculatorService
    </specification-id>
    <implementation-class>
        com.example.dsc.sample.facade.SimpleCalculatorFacade
    </implementation-class>

Notice that the specification-id points to the service interface. This is a requirement for the script to run properly. Also, adobe-bb-cmn-lib.jar needs to be present inside the DSC and added to its class-path in the component.xml:

    <class-path>
        <![CDATA[adobe-bb-cmn-lib.jar simplecalculator.jar]]>
    </class-path>

We need to add adobe-bb-cmn-lib.jar to our build path:

    <path id="SimpleCalculator.classpath">
        <pathelement
path="${lcsdk.clientlib.common}/adobe-livecycle-client.jar"/>
        <pathelement
path="${common.dist.dir}/lib/adobe-bb-cmn-lib.jar" />
        <pathelement
path="${lcsdk.clientlib.tp}/commons-logging.jar" />
    </path>

We can now invoke the gen-facade target before the compile target to generate Facade code into the gen-facade folder. The code from gen-facade needs to be compiled into the DSC Jar.

    <target name="compile" depends="gen-facade">
        <javac srcdir="src:gen-facade" destdir="build"
debug="on" target="1.5">

Next we will add the code that actually contains interception logic we want to apply. Interception in CMN hangs off two interfaces: InterceptorSupport which is the actual interface that contains the actual interception business logic and InterceptedCallback which allows the original business logic to be invoked. Any service using CMN never has to implement IntercepterCallback - an implementation of this is always passed in to the interceptor by the facade. So we have our interceptor class:

public class SimpleInterceptor implements InterceptorSupport {
    private static final Log log =
LogFactory.getLog(SimpleInterceptor.class);
   
    public static SimpleInterceptor getInstance() {
        return new SimpleInterceptor();
    }
   
    public Map<String, Object> invoke(String serviceName,
String methodName,
            Map<String, Object> input, InterceptedCallback
callback, 
            String outputName) throws SAException {
       
        Map<String, Object> result = new HashMap<String,
Object>();
       
        log.info("Before invoking: " + methodName + " of Service: "
+
                serviceName + ", with inputs: " + input);
       
        Object output = callback.invoke(serviceName, methodName,
input);
       
        log.info("Invocation result: " + output);
       
        if (outputName != null) {
            result.put(outputName, output);
        }
     
        return result;
    }
}

This simply adds a log statement before and after any service invocation. Once callback.invoke() is called, the result of the invocation must be placed into the result map with the provided outputName. If the outputName is null, this means the method returns void. Since the interceptor gets all the inputs and the outputs, both pre and post interception can be performed. The static getInstance() method is a requirement - it must be present for the facade to compile and work.

Deploy the resulting DSC to your LC Server and invoke the sum() operation to see logs of the form:

2009-10-23 17:14:39,820 INFO
[com.example.dsc.sample.interceptor.SimpleInterceptor] Before
invoking: sum of Service: SimpleCalculatorService, with inputs:
{num2=2.0, num1=1.0}
2009-10-23 17:14:39,853 INFO
[com.example.dsc.sample.interceptor.SimpleInterceptor] Invocation
result: 3.0

Notice that we did not have to modify our implementation to get these logs - also, if we had a DSC containing multiple services with many operations, we would not have to do anything else to enable logging of those operations also, except to list out the services in the facade.properties and map the facade class to the service in the component.xml in place of our actual implementation class.

Code

The code for this sample is attached. Additionally, it is also configured so that you can use the DSCInstall and DSCRemove tasks in the build file to automatically deploy this DSC to a LiveCycle server.

More Information

For more information on the Solution Accelerator program in general, see here.

You can find information on the Common Building Block here and access documentation related to it and other Solution Accelerators here.


+
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

Report abuse

Related recipes