While the DCI markup allows MCS to access information from multiple data sources and integrate it with XDIME pages, it is not intended to perform advanced data processing. Authors can, however, use Java beans to separate business logic from the presentation layer, and create rich XDIME applications that perform sophisticated data processing. For example, an application can call a method on a bean that returns the data, and the DCI markup can be used to present the data on an XDIME page.
While the DCI markup allows MCS to access information from multiple data sources and integrate it with XDIME pages, it is not intended to perform advanced data processing. Authors can, however, use Java beans to separate business logic from the presentation layer, and create rich XDIME applications that perform sophisticated data processing. For example, an application can call a method on a bean that returns the data, and the DCI markup can be used to present the data on an XDIME page.
Java beans are self-contained, reusable elements of software. In the context of DCI a Java bean is a Plain Old Java Object (POJO) that is created and invoked through reflection, and therefore does not have to implement any special interfaces or extend any special classes. If required a bean can indicate through constructor and method signatures that it needs access to the specific pipeline classes.
Beans can be created and initialized from outside the pipeline, for example, by a code that sets up and initializes the pipeline, or from within the pipline by the bean:create operation. In the first case, it is the responsibility of the code to create and initialize the bean, then to make it available for use within the pipeline as a variable, and finally to release its resources, i.e. the pipeline is only responsible for calling methods on the bean. There are no restrictions on how to create and initialize beans. For example, it could be an existing object that is simply made available for use within the pipeline, or an existing object could be wrapped in another object and the wrapped object could be passed to the constructor. Once the bean has been initialized, then it must be set as the value of a variable. The following example shows how an external code can add a bean to the expression context before the pipeline is processed. Please note that beans provided by an external code are represented as variables in the global scope, but the application that creates the pipeline can decide how to scope and persist beans, i.e. it is possible to scope beans to various scopes. However, MCS does not provide specific support to control the scope of beans, i.e. there is no way within the DCI markup to specify the scope within which a bean should be created.
package com.volantis.openapi.pipeline.samples.beans;
import com.volantis.xml.expression.ExpressionContext;
import com.volantis.xml.expression.ExpressionFactory;
import com.volantis.xml.expression.ExpressionScope;
import com.volantis.xml.expression.Variable;
import com.volantis.xml.expression.Value;
import com.volantis.xml.expression.ExpressionException;
import com.volantis.xml.expression.atomic.ObjectValue;
import com.volantis.xml.namespace.ExpandedName;
public class ExternalBeanManager {
/**
* Add the supplied bean as a variable to the supplied expression context.
*
* @param context The expression context to be updated.
* @param name The name of the variable used to access the bean.
* @param bean The bean.
* @throws ExpressionException If there was a problem during conversion.
*/
public void addBean(ExpressionContext context, ExpandedName name, Object bean)
throws ExpressionException {
// Get the factory for creating expression related objects.
ExpressionFactory factory = context.getFactory();
// Wrap the bean in an ObjectValue for use within the expressions.
Value value = factory.asValue(bean);
// Get the global expression scope.
ExpressionScope scope = context.getGlobalScope();
// Create a variable in the global scope.
Variable variable = scope.declareVariable(name, value);
}
}
The bean:create operation creates, initializes, and releases a bean from within the pipeline. In this case, the pipeline is responsible for managing the whole life cycle of a bean, i.e. the pipeline has a built-in bean manager that handles all interactions with beans.
The bean manager looks for a constructor that matches one of the following signatures.
public <class>(ExpressionContext context)
public <class>()
The first signature provides access to contextual information that the selected bean can use. The second signature represents the default constructor. If both types of constructors are specified, the one providing access to contextual information will be used.
Once the new bean has been created, the manager passes initialization properties to the bean by invoking appropriately named and typed setter methods. The order in which the setters are invoked is the same as the order in which the properties are specified in the XDIME page. Please note that the names of properties are case insensitive, and the same property cannot be specified twice.
<bean:create
class="com.volantis.openapi.pipeline.samples.beans.CalculatorBean"
name="calculator">
<bean:property name="initial" value="2"/>
</bean:create>
When all the setters have been called, then the bean manager searches for the void initialize() method. If it is not found, or found but does not have the correct signature, then it is assumed that the bean has no initialization to perform. Otherwise, the method is invoked.
At this point the bean has been constructed and needs to be made available for use by expressions. This is done by creating a variable with the name specified by the name attribute of a bean:create element. It is an error if the variable already exists in the selected scope; though the variable may shadow a variable in a containing scope.
Beans can be shared across contexts. However, it is the responsibility of a bean to manage concurrent access.
When the expression scope in which the bean was created is released, then the bean is also released. The bean manager invokes the void release() method to allow the bean to release its resources. If the method is not found, or found but does not have the correct signature, then it is assumed that the bean has no resources to release and so is silently ignored.
The bean:call() function calls a method on a bean. The method must specify the name of a bean whose method is to be invoked, the name of a method to invoke, and optionally a list of arguments that should be passed to the method. The Java method must have the correct number of parameters to match the arguments passed into the method. If the Java method does not itself accept a variable number of arguments, then the number of supplied arguments must exactly match the number of parameters. If the Java method is a variable argument function, then the number of supplied arguments must be greater than or equal to the number of fixed parameters. Any additional arguments are then placed in the variable argument array.
The following example presents a page that uses a Java bean to perform simple calculations. The name attribute of the bean:create element specifies the name of the variable which must be created to reference the bean. Its class attribute specifies the name of the Java bean class to instantiate. The pipeline:value-of elements are used to insert the values returned by the bean:call function into the page. Please note that processing of beans can be integrated with the pipeline:cache operation.
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/2002/06/xhtml2"
xmlns:pipeline="http://www.volantis.com/xmlns/marlin-pipeline"
xmlns:bean="http://www.volantis.com/xmlns/2010/02/dci/bean">
<head>
<title>bean:create</title>
</head>
<body>
<div>
<pipeline:try>
<pipeline:preferred>
<!-- Create the bean. -->
<bean:create
class="com.volantis.openapi.pipeline.samples.beans.CalculatorBean"
name="calculator">
<bean:property name="initial" value="2"/>
</bean:create>
<!-- Perform some calculations, (2 + 10) * 3 and insert the result in the page. -->
<pipeline:value-of expr="bean:call($calculator, 'add', 10)"/>
<pipeline:value-of expr="bean:call($calculator, 'multiply', 3)"/> Total:
<pipeline:value-of expr="bean:call($calculator, 'getTotal')"/>
</pipeline:preferred>
<!--
! At this point the scope containing the calculator bean and its variable has just
! been released so the bean is no longer alive and the variable no longer exists.
!-->
<pipeline:alternative> Oops </pipeline:alternative>
</pipeline:try>
</div>
</body>
</html>
Where, com.volantis.openapi.pipeline.samples.beans.CalculatorBean contains the following code.
package com.volantis.openapi.pipeline.samples.beans;
public class CalculatorBean {
private int total;
public void setInitial(int initial) {
total = initial;
}
public void add(int n) {
total += n;
}
public void clear() {
total = 0;
}
public void multiply(int n) {
total = total * n;
}
public int getTotal() {
return total;
}
}
Java beans must be compiled with JDK version 1.5.0_x.
You need to ensure that beans are in your CLASSPATH.
Please note that if the first parameter of the Java method is of type ExpressionContext, then it can be passed the context within which the function is being executed. This should typically be used with externally created beans that can be used across a number of different contexts and need access to the context from which they are being called. Beans created by the bean:create operation do not need to use this mechanism to get access to the context as they are only usable within one context which can be provided in the constructor. However, they can use the method previously described, for example, if the same class is used internally and externally.