While working on a LiveCycle project with multiple services, you may require the addition of an aspect/interceptor to all your services.
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
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.
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:
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.
+