Customizing the Expression Manager building block

Expression Manager building block functionality is exposed via a set of Spring beans and Flex components. Because the services are exposed as Spring beans, you can replace the services with your own custom implementation.

Customizing the default Client-Side ExpressionEvaluator service

If you want to change the ExpressionEvaluator service, but don’t want to replace it with a new custom implementation, you can customize the default implementation. The default implementation of the ExpressionEvaluator service requires an instance of Variable Resolver and Function Mapper. These instances are used to retrieve the values of variables and resolve function references when expressions are evaluated. You can configure them using the following properties of the default ExpressionEvaluator service:
  • variableResolver: The client-side variable resolver is a representation of the com.adobe.solutions.exm.runtime.IVariableResolver interface. This interface is more complex than its server-side counterpart. Apart from the standard API for returning the value of a variable, it also has an API for setting the value of a variable used by Expression Manager. This API is used for setting target variable values when target variables are also passed along with expressions to the manager). An implementation of the resolver interface also dispatches an event (an instance of com.adobe.solutions.exm.runtime.VariableChangeEvent) of type variableChangeEvent. The event is dispatched whenever the value of any of the variables managed by the resolver changes. Expression Manager uses this event for recomputing expressions which include the changed variable). This event is expected to contain the fully qualified name of the variable whose value changed, the old value, and the new value.

    Expression Manager provides a simple resolver implementation (com.adobe.solutions.exm.runtime.impl.SimpleVariableResolver), backed by a dictionary, and uses fully qualified variable names as keys in the dictionary. This implementation is likely unusable for all but the most trivial of use-cases.

  • functionMapper: The client-side function mapper uses the com.adobe.solutions.exm.runtime.IFunctionMapper interface. This interface has the same contract as its server-side counterpart. Given a function prefix and local-name, it returns a Function instance which can be called by Expression Manager. The client-side expression manager supports both remote and local functions. The function instance returned by the mapper behaves differently depending on the type of function:
    • Remote functions: Given a prefix of the form REMOTE_<svc> and a local-name. The mapper returns a Function instance corresponding to the operation with the same name as the local-name on the Flex remoting destination with ID <svc>. The required remoting configuration is implemented in the application by supplying a suitable services-config.xml during compilation time.

    • Local functions: Given a prefix and local-name, the mapper tries to resolve them to a method defined on an ActionScript class by consulting an internal mapping. The application must ensure that the resolved ActionScript class is available in the current ApplicationDomain and has a no-argument constructor. This internal mapping can be populated in one of the following ways (or a combination of both):

      • The application can call addFunction() on the SimpleFunctionMapper instance used by the expression manager for adding entries to the internal mapping. The first parameter of the method is the function reference as encountered inside expressions (a string of the form "<prefix>:<local_name>"). The second parameter is the ActionScript method to which the function maps (a string of the form "<fully_qualified_ActionScript_class_name>:<method_name>").

      • The application can also have the SimpleFunctionMapper instance initialize these mappings from the server. To do so, call the SimpleFunctionMapper constructor with the initFromServer parameter = true. The server invocation is asynchronous, and so the application must wait for the SimpleFunctionMapper instance to dispatch an Event of type serverInitializationSuccess before the instance can be used. If server invocation fails, the SimpleFunctionMapper instance dispatches an Event of type "serverInitializationError" to notify the application. Also, server invocation involves loading a Flex remoting destination with ID "lc.exm.functionService". The application must ensure that the configuration for this destination (and its channels) is configured by supplying a suitable services-config.xml at compilation time.

Adding custom remote functions

You can create a custom bundle to export your own remote functions for use inside expressions. To create a custom bundle to export your own remote functions, perform the following tasks:
  1. Ensure that the Expression Manager classes, specifically the com.adobe.exm.expeval package, are imported in the bundle's manifest.

  2. Define an interface for the OSGi service containing methods which are being exported for use by Expression Manager.

  3. Declare methods on the interface and annotate them with the @ServiceMethod annotation (com.adobe.exm.expeval.ServiceMethod). Expression Manager ignores any unannotated methods.

    The ServiceMethod annotation has the following optional attributes which can also be specified:
    • enabled: Determines if this method is enabled. Expression Manager ignores disabled methods. If unspecified, the default value of this attribute is true.

    • familyId: Specifies the method’s family (group). If empty, Expression Manager assumes that the method belongs to the default family. There is no registry of families (except for the default one) from which functions are chosen. Expression Manager dynamically creates the registry by taking a union of all family IDs specified by all the functions exported by the various bundles. Ensure that the ID specified here is reasonably human-readable, since it displays in the expression authoring user interface as well.

    • displayName: A human-readable name for the function. This name is used for display purposes in the authoring user interface. If empty, Expression Manager constructs a default name using the function's prefix and local-name.

    • description: A verbose description for the function. This description is used for display purposes in the authoring user interface. If empty, Expression Manager constructs a default description using the function's prefix and local-name.

    The parameters of the methods can also be optionally annotated using the @ServiceMethodParameter annotation (com.adobe.exm.expeval.ServiceMethodParameter). This annotation is only used for specifying human-readable names and descriptions of method parameters for use in the authoring user interface.

  4. Ensure that the parameters and return-values of the interface methods belong to one of the supported types. A list of supported types can be found in Common JSP EL examples.

  5. Define the implementation of the interface, configure it as a Spring bean and export it as an OSGi service via BluePrint with the following special properties:
    <bp:service interface="A" ref="<bean_id_of_AImpl> "> 
        <bp:service-properties> 
            <entry key="connectors.httpinvoker" value="true" /> 
            <entry key="connectors.httpinvoker.alias" value="/<service_id> " /> 
            <entry key="connectors.remoting" value="true" /> 
            <entry key="connectors.remoting.id" value="<service_id>" /> 
            <entry key="exm.service" value="true" /> 
        </bp:service-properties> 
    </bp:service>

    The exm.service=true entry instructs Expression Manager the service contains remote functions suitable for use in expressions. The <service_id> value must be a valid Java identifier (alphanumeric/$/_ with no other special characters). This value, prefixed with the REMOTE_ keyword, forms the prefix which is used inside expressions. For example, an interface with an annotated method bar() and the service ID in the BluePrint service properties is foo, the method can be referenced inside expressions using REMOTE_foo:bar().

Adding custom local functions

You can create a custom bundle to export your own local functions for use inside expressions. To create a custom bundle to export your own local functions, perform the following tasks:
  1. Ensure that the Expression Manager classes, specifically the com.adobe.exm.expeval package, are imported in the bundle's manifest.

  2. For each logical local function, ensure that a static method is defined on a Java class and an equivalent normal method defined on some ActionScript class. For example, for local function f, assume that the Java implementation is com.foo.SomeJavaClass.fn() and the ActionScript implementation is com.foo.SomeASClass.flexFn().

  3. Package the Java class in the bundle which is exporting the local function and ensure that it is marked as exported in the bundle's manifest. Otherwise the Expression Manager bundle cannot load the class. On the client-side, ensure that the ActionScript class is available in the ApplicationDomain in which the Expression Manager libraries are being used.

  4. Annotate the Java implementation of the local function with the @LocalFunction annotation (com.adobe.exm.expeval.LocalFunction). Expression Manager ignores unannotated methods.

    The LocalFunction annotation has the following optional attributes which can also be specified:
    • FlexClassName: The fully qualified name of the ActionScript class hosting the Flex implementation of the local function.

    • FlexMethodName: The name of the ActionScript method corresponding to the local function's Flex implementation.

    • enabled: Determines if this method is enabled. Expression Manager ignores disabled methods.

    • familyId: Specifies the method’s family (group). If empty, Expression Manager assumes that the method belongs to the default family. There is no registry of families (except for the default one) from which functions are chosen. Expression Manager dynamically creates the registry by taking a union over the set of all family IDs specified by all the functions exported by the various bundles. Ensure that the ID they specify here is reasonably human-readable, since it displays in the expression authoring user interface as well.

    • displayName: A human-readable name for the function. This name is used for display purposes in the authoring user interface. If empty, Expression Manager constructs a default name using the function's prefix and local-name.

    • description: A verbose description for the function. This description is used for display purposes in the authoring user interface. If empty, Expression Manager constructs a default description using the function's prefix and local-name.

    The parameters of the methods can also be optionally annotated using the @LocalFunctionParameter annotation (com.adobe.exm.expeval.LocalFunctionParameter). This annotation is only used for specifying human-readable names and descriptions of method parameters for use in the authoring user interface.

  5. Ensure that the parameters and return-values of the interface methods belong to one of the supported types. A list of supported types can be found in Common JSP EL examples.

  6. Define an empty bean of type com.adobe.exm.expeval.LocalFunctionProviderImpl in the Spring context and assign it an ID.

  7. Export the bean as an OSGi service with the following special properties:
    <bp:service interface="com.adobe.exm.expeval.LocalFunctionProvider" ref="lfp_id "> 
        <bp:service-properties> 
            <entry key="exm.local.function.entry.<prefix> " value="<fully_qualified_name_of_Java_class_hosting_local_function> " /> 
            [Repeat entries of the form "exm.local.function.entry.*" for other classes hosting local functions, if any] 
        </bp:service-properties> 
    </bp:service>

Ensure that the Java classes being specified as values for the exm.local.function.entry.* keys are exported in the manifest of the host bundle, otherwise the Expression Manager bundle cannot load them. The prefixes specified are used as-is as function-prefixes inside expressions, and hence must be valid Java identifiers. For example, if localFoo is the prefix for the com.foo.SomeJavaClass class, then you can see local function in expressions using the notation localFoo:fn(). The local-name in the expression is the same as the Java method name.

Evaluating an expression with a custom function

The following code example shows how to call an expression containing a custom function:

HashMap<String, Serializable> variables = new HashMap<String, Serializable>(); 
variables.put("number1", 25); 
variables.put("number2", 26); 
EXPEvaluator expressionEvaluationService = null; 
// Retrieve instance of expressionEvaluationService from spring // context as per example given in section 4. 
Object result = expressionEvaluationService.evaluateExpression( "${fm1:add(number1, number2)}", variables);

When evaluating expressions, the function name would be prefixed with the key (fm1). When calling an "add" function, the EL prefixes with fm1, for example, "${fm1:add(number1, number2)}".

// Ethnio survey code removed