The programming philosophy of JAC is to let the functional (core-business) program components free from any reference towards all the technical or business extensions that it needs to be effective. Thus, JAC functional components do not have to implement special interface or extend special classes. All this is done seamlessly by the JAC container. In J2EE world, this kind of containers begin to appear with POJOs (Plain Old Java Object) support. However, AOP (the core technology of JAC) allows better separated concern and naturally fully functional objects.
It can be troubleshooting for the programmer when s/he makes its first JAC program. The idea is to concentrate on the core functionalities and to make a program as if you were a very beginner in Java. For instance, even if you know that you will need to display a given component or that a given component will need to be persistent, just program a regular Java class and do not try to import any packages that deal with persistence or GUI, all these will be done in a second step and will not require any reengineering.
This magic stuff is possible because of the aspect-oriented framework implemented by JAC. This framework builds a set of classes and objects that allows the programmer to define new aspects or use existing ones in a very simple fashion. Once configured, the aspects will be able to deal with your components the way you expect and introduce new concerns on them (e.g. persistence, transaction, distribution, fault-tolerance, ...).
JAC is a framework for AOP. All concepts are expressed in pure Java, without any syntactic extension. The following section introduces these concepts.
An aspect component (AC for short) is a subclass of
org.objectweb.jac.core.AspectComponent. There is no other requirement.
Hence, the simplest, yet useless, AC is:
import org.objectweb.jac.core.AspectComponent;
public class MyFirstAC extends AspectComponent {
// ....
}
ACs' main role is to define pointcuts. We will see later in section Aspect component configuration that AC configuration is an important task for which JAC provides some specific features.
A pointcut is a structure that denotes a set of so called joinpoints. A joinpoint is a place in a program execution where an AC needs to execute some code. Hence, by defining a pointcut for an AC, ones define the locations where an AC is woven.
Pointcuts are defined in an AC by calling the pointcut method
inherited from
AspectComponent. The role of pointcuts is to filter the set of all
methods of a base program and to select the ones where the AC is woven. Hence,
four kinds of parameters are required for the
pointcut method:
customer#2 is name of the 3rd instance of class Customer.addItem(java.lang.String,int):voidThe following AC illustrates the definition of a pointcut.
import org.objectweb.jac.core.AspectComponent;
public class MyFirstAC extends AspectComponent {
public MyFirstAC() {
pointcut(
// object, class & method regexps
// ------------------------------
".*", // all objects
"Command", // class Command
"addItem.*", // all addItem methods
// wrapper class name
// ------------------
"MyWrapper",
// Exception handler
// -----------------
null, // no handler
// wrapper cardinality
// -------------------
false // singleton
);
}
}
Other signatures are available for the pointcut method:
pointcut:
an array of objects can follow the wrapper class name. They specify the
parameters to be used when the wrapper is instanciated (this is done by the
framework).
pointcut:
an instance of
Wrapper
can be used. This is the instance to be used for all joinpoints. In such a
case there is no wrapper class name parameter, and no wrapper cardinality
parameter.Besides the existing GNU regexp operators, JAC provides a set of additional functions that can be used in pointcut expressions.
For all pointcut expressions, the keywords AND, OR,
NOT and ALL (synonym for .*) may be used.
For method pointcut expressions, the keywords ACCESSORS,
MODIFIERS, GETTERS(list), SETTERS(list),
ADDERS(list), REMOVERS(list) are available. Their definition
follows.
ACCESSORS: select methods that read one or several fields of
their class.MODIFIERS: select methods that write one or several fields of
their class.GETTERS(list) where list is a comma-separated list of field
names: select methods that return the value of one of the fields specified in
the list.SETTERS(list) where list is a comma-separated list of field
names: select methods that write at least one of the fields specified in the
list. The value written must come from one of the method parameters.As a matter of example, consider the class:
public class Person {
private String name;
private int age;
public void birthday() { age++; }
public String whatIsYourNamePlease() { return name; }
public void foo(String first,String last) { name=first+last; }
public void bar(String name) { this.name=name; }
}
ACCESSORS: select whatIsYourNamePlease and
birthday. The former reads name, and the latter reads
age.MODIFIERS: select birthday, foo and
bar. These 3 methods modify at least one field of Person.GETTERS(name): select whatIsYourNamePlease that
returns the value of field name.SETTERS(name): select bar that writes at field
name. The value written comes from the method parameters. Method
foo also writes field name. However the value
written does not come from a direct parameter but is the result of a
computation. foo is then not considered as a setter. It is
however a modifier.The 2 remaining keywords concern java.util.Collection fields.
ADDERS(list) where list is a comma-separated list of field
names: select methods that add an element in at least one of the fields
specified in the list. The fields must implement the
java.util.Collection interface.REMOVERS(list) where list is a comma-separated list of field
names: select methods that remove an element in at least one of the fields
specified in the list. The fields must implement the
java.util.Collection interface.Whether a method fits into one or several of these categories, is automatically determined by the framework. When a class is loaded, its bytecode is analyzed and each method is associated with tags that qualify is as an accessor, a modifier, etc. These attributes are stored as metadata (see the Reflective features section).
A wrapper is a subclass of org.objectweb.jac.core.Wrapper
that implements two methods: construct and
invoke. The signatures of construct and
invoke conform
to the AOP
Alliance API. As explained in the previous section, a wrapper instance is
associated to a pointcut.
construct intercepts constructor
executions of all the classes that are specified in the pointcut,invoke intercept executions of all the methods
specified in the pointcut.import org.aopalliance.intercept.ConstructorInvocation;
import org.aopalliance.intercept.MethodInvocation;
import org.objectweb.jac.core.AspectComponent;
import org.objectweb.jac.core.Wrapper;
public class MyWrapper extends Wrapper {
// a wrapper must implement Wrapper(AspectComponent)
// and call super
// -------------------------------------------------
public MyWrapper(AspectComponent ac) {
super(ac);
}
// constructors interceptor
// ------------------------
public Object construct(ConstructorInvocation ci) throws Throwable { ... }
// methods interceptor
// -------------------
public Object invoke(MethodInvocation mi) throws Throwable { ... }
}
Both construct and
invoke can specify before and after code to be run around their
associated joinpoints. These two parts are separated by a call to the
proceed method inherited from
Wrapper.
public Object invoke(MethodInvocation mi) throws Throwable {
// -----------
// Before code
// -----------
Object ret = proceed(mi);
// ----------
// After code
// ----------
return ret;
}
proceed calls the intercepted method or constructor, or the next wrapper
when several wrappers advice the same joinpoint (see section
Wrappers ordering for more details). Both construct and
invoke are mandatory, even if no interception is needed. In such a
case, simply returning the value returned by
proceed is sufficient.
The value returned by
proceed is generally returned as this by construct and
invoke. However this value can be changed if one needs it.
The
MethodInvocation (resp.
ConstructorInvocation) parameter reify the method (resp.
constructor) execution. It provides information about the current execution.
Among other available methods:
getMethod() (resp.
getConstructor()): returns the java.lang.reflect.Method
(resp. Constructor) whose execution is intercepted.getArguments(): returns the arguments of the call as an array
of objects. The elements of this array can be changed if one needs it.getThis(): returns the wrappee, i.e. the object where
the execution has been intercepted.To show how wrappers can be used, here are 9 simple examples
of very classical
invoke methods. Note that
invoke can access the wrapper state that is not represented in
the following examples.
A verbose wrapper (that prints a trace on the screen --- easily modifiable to print in a log file).
Object ret = null;
System.out.println("<< calling "+mi.getMethod().getName()+ " with "+mi.getArguments()+">>");
ret = proceed(mi);
System.out.println("<< "+mi.getMethod().getName()+" returned "+ret+">>");
return ret;
A wrapping method that performs some precondition test on the arguments (here, the first argument is an integer and must be bounded within 1-100).
Object ret = null;
Object[] args = mi.getMethod().getArguments();
if (((Integer)args[0]).intValue() >= 0 &&
((Integer)args[0]).intValue() <= 100) {
ret=proceed(mi);
}
else {
throw new ArgOutOfBoundsException();
}
return ret;
Almost the same wrapper but, that corrects the argument value instead of throwing an exception.
Object[] args = mi.getMethod().getArguments(); if (((Integer)args[0]).intValue() < 0) args[0] = new Integer(0); if (((Integer)args[0]).intValue() > 100) args[0] = new Integer(100); return proceed(mi);
A wrapper that stores the object state within a database (to make it persistent).
Object ret = null; ret = proceed(mi); Database d = new Database(); d.connect(); Wrappee wrappee = mi.getThis(); d.write(wrappee, Utils.getObjectState(wrappee)); return ret;
A wrapper that performs a postcondition test on the return value (in this case, the return value must be an integer bounded within 0 and 100).
Object ret = null;
ret = proceed(mi);
if (((Integer)ret).intValue() < 0 || ((Integer)ret).intValue() > 100) {
throw new RetOutOfBoundsException()
}
return ret;
A wrapper that performs a test on the state of the wrappee after the base method has been called.
Object ret = null;
ret = proceed(interaction);
if ( ((Integer)ClassRepository.get()
.getClass(mi.getThis())
.getField("aField").get()).intValue() < 0 ||
((Integer)ClassRepository.get()
.getClass(mi.getThis())
.getField("aField").get()).intValue() > 100)
{
throw new FieldOutOfBoundsException();
}
return ret;
A wrapper that forwards the call to a set of replicas (e.g. to implement a fault-tolerance aspect).
Object ret = null;
// replicas is a RemoteRef[] field of the wrapper
for (int i = 0; i < replicas.length; i++) {
((Interaction)mi).invoke(replicas[i]);
}
return proceed(mi);
A wrapper that forwards the call to a remote object
(acts like a proxy). Note that this wrapper does not call the proceed
method so that the wrappee is never called.
return remoteRef.invoke(mi.getMethod(),mi.getArguments());
A wrapper that caches the results of the wrappee and that uses the cached values when the arguments and the wrappee state have already been used.
Object ret = null;
if (isCacheHit(mi.getThis(), mi.getArguments())) {
ret = getCachedValue(mi.getThis(), mi.getArguments());
} else {
ret = proceed(mi);
setCachedValue(mi.getThis(), mi.getArguments(), ret);
}
return ret;
An important issue when trying to reuse aspects is to be able to configure them. For instance, a transaction aspect needs to specify which methods are to be executed with ACID propertiers, or a persistence aspect needs to specify a JDBC URL where data can be saved. Instead of hard-coding these information in classes, they are externalized in files. Their values can then be changed without having to recompile the application.
In traditional application server (AS for short) such as J2EE, configuration is performed with properties and values set in XML files. This solution is sufficient for AS with a closed set of technical services. However, with an open AS such as JAC where new aspects can be programmed, one can not foreseen all the configuration parameters that will be needed. Hence, a more powerful configuration mecanism is required.
Configuring an aspect with JAC consists in defining a sequence of calls to the public methods of the AC. Hence, this is the responsability of the AC developer to define such public methods, and thus, to define what can be configured in an AC.
Configurations are defined in aspect configuration files. These are text files with the .acc extension where each line corresponds to the call of a public method of the AC. The call may contain a list of space-separated parameters. String values can be specified as this, or be put between double-quotes in case of ambiguity (for instance if there is a space in the string). Array parameters are put between brackets. Each line ends with a semicolon.
For instance, the following gui.acc file:
setTitle myGUI "Invoices";
setSubPanesGeometry myGUI 2 HORIZONTAL {false,false};
setPaneContent myGUI 0 Object {"default","invoices#0"};
specifies calls for the setTitle, setSubPanesGeometry,
setPaneContent methods of the org.objectweb.jac.aspects.gui.GuiAC AC.
These calls are executed in the specified order when the AC is instantiated (this
is done by the framework). Each aspect is a singleton (except when distribution
comes into play, where there is one instance of the AC per site). The link
between a .acc file and an AC is specified in the application descriptor (see
section Application descriptors).
All kinds of parameters can be made configurable, including pointcut
definitions. For instance, a common pattern when programming an AC consists in
defining a method such as addTrace.
import org.objectweb.jac.core.AspectComponent;
public class TraceAC extends AspectComponent {
public TraceAC() {}
public void addTrace( String objects, String classes, String methods ) {
pointcut(
objects, classes, methods,
"TraceWrapper",
null, false
);
}
}
Pointcut definitions are then no longer hard-coded in AC classes, and can dynamically be configured. Then the trace.acc configuration file may specify:
addTrace ".*" "Customer" "addItem.*" ; addTrace ".*#0" "Item.*" ".*" ;
Value grouping may be used whenever a parameter value is repeated in many calls to configuration methods. It allows to save time by avoiding the repeat. The definition of value grouping in a .acc file follows:
class valueToBeGrouped {
call1 .... ;
call2 .... ;
...
}
valueToBeGrouped will be inserted as the 1st parameter of
all the calls between brackets. Furthermore, class is not a keyword,
this is just an identifier to declare a grouping. Any other identifier may be
used: window, foo, bar, etc.
Value groupings can be nested, i.e. a grouping can be defined inside another one.
An application descriptor is a text file with the .jac extension that gives basic information about a business application and its aspects. This file defines proprerties and values that are read by the framework in order to launch an application. There are 4 main properties that needs to be defined:
applicationName: a string describing the applicationlaunchingClass: the main business class containing the main
methodaspects: the aspects initially defined for the applicationjac.acs: the definition of these aspectsAssuming the business application main class is mypackage.Run,
the following myApp.jac application descriptor defines:
applicationName: My first JAC application
lauchingClass: mypackage.Run
aspects: \
traceid trace.acc true \
persistenceid persistence.acc true
jac.acs: \
traceid TraceAC \
persistenceid somepackage.PersistenceAC
that 2 aspects are initially woven, one for trace, and one for persistence.
More formally, the property aspects defines three values for
each aspects associated to the application: the 1st one is a freely-chosen
identifier, the 2nd one is the name of the aspect configuration file, the 3rd
one is a flag that says whether the aspect is initialy woven or not. The
jac.acs property gives for each identifier defined in aspects,
the AC class that implements the aspect.
Note that property values follow the colon after the property name and ends at the end of line. The \ character allows to continue property values on several lines.
To run the application then use org.objectweb.jac.jar.
java -jar jac.jar -C myClassPath myAppPath/myApp.jac arg1 ...
To know about the launching options, use the command java -jar jac.jar
-h or refer the JAC launching class org.objectweb.jac.core.Jac.
A role method introduces a new feature in an application. A role method is
defined in a wrapper. The only constraint on its signature is that the first
parameter must by of type
org.objectweb.jac.core.Wrappee. For instance, the following wrapper
defines the printAmountWithHeader role method:
import org.aopalliance.intercept.ConstructorInvocation;
import org.aopalliance.intercept.MethodInvocation;
import org.objectweb.jac.core.AspectComponent;
import org.objectweb.jac.core.Wrapper;
public class MyWrapper extends Wrapper {
// ------------------------------------------
// A role method for the Order business class
// ------------------------------------------
public void printAmountWithHeader( Wrappee wrappee, String header ) {
double amount = ((Order)wrappee).computeAmount();
System.out.println(header+amount);
}
public MyWrapper(AspectComponent ac) { super(ac); }
public Object construct(ConstructorInvocation ci) throws Throwable { ... }
public Object invoke(MethodInvocation mi) throws Throwable { ... }
}
Role methods are invoked with the method invokeRoleMethod. This method takes 3 parameters: the reference of the wrappee for which the call of the role method is to be made, the name of the role method to call, and the parameters of the call as an array of objects.
For instance, the previous printAmountWithHeader
role method can be called like this:
Wrapping.invokeRoleMethod(
(Wrappee)anOrder, // the wrappee where the call is to be made
"computeAmountAndPrint", // the role method to call
new Object[]{">> "} // the parameters of the call
);
An exception handler is a method that handles exception thrown in the context
of a pointcut. An exception handler is a method defined in a wrapper and
declared in the call to the
pointcut
method. The following signature is mandatory for an exception handler:
public Object name( Exception e ) throws Exception;
The rationale between exception handlers is to let programmers define handlers dedicated to some types of exceptions (for instance IOException, SocketException, etc.). Hence, an handler may treat a given type and re-throw other types of exceptions.
Exception handlers handle exception thrown in the context of a pointcut. Such a context encompasses the business method or constructor executions that are designated by the pointcut and all the wrappers woven around these pointcuts.
The following AC and wrapper define the myIOExceptionHandler
exception handler.
import org.aopalliance.intercept.ConstructorInvocation;
import org.aopalliance.intercept.MethodInvocation;
import org.objectweb.jac.core.AspectComponent;
import org.objectweb.jac.core.Wrapper;
public class MyFirstAC extends AspectComponent {
public MyFirstAC() {
pointcut(
".*", "Command", "addItem.*",
"MyWrapper",
// Exception handler
// -----------------
"myIOExceptionHandler",
false
);
}
}
public class MyWrapper extends Wrapper {
// ------------------------------------
// An exception handler for IOException
// ------------------------------------
public Object myIOExceptionHandler( Exception e ) throws Exception {
System.out.println( "Exception "+e.getMessage()+" thrown" );
if ( ! (e instanceof java.io.IOException) )
throw e;
return null;
}
public MyWrapper(AspectComponent ac) { super(ac); }
public Object construct(ConstructorInvocation ci) throws Throwable { ... }
public Object invoke(MethodInvocation mi) throws Throwable { ... }
}
Several exception handlers may be defined for the same joinpoint in different pointcuts. In such a case, the framework sequentially calls all the handlers until the exception is no longer propagated.
JAC is distributed with a library of xx existing AC. Reusing an aspect can be simply done by writing the configuration files that will adapt the AC to your current application.
Existing AC can be classified in 5 categories: GUI, persistence and transaction, distribution, monitoring and miscellaneous.
For data persistence, 2 AC are provided:
For transactions, 2 AC are also provided:
The following AC address issues related to distribution. See also section Distributed programming for more information on this topic.
JAC offers features that extends the reflective features of J2SE.
The org.objectweb.jac.core.ObjectRepository class
provides a repository of all existing objects in an application.
The org.objectweb.jac.core.rtti package offers the ability to introspect and query program elements
(classes, methods, fields, constructors). Metadata can be set (see
setAttribute(String,Object)) and queried (see
getAttribute(String)) on these elements.
Class ClassRepository is the entry point for this package.
The attrdef(String,Object) method allows to define attributes that will be accessible until the thread ends. Symetrically, the attr(String) method allows to query thread attributes map to retrieve the value of an attribute.
This feature is useful when one wants to transmit information accross method calls, for instance from a callee to a caller, or to a method further down in the call stack.
Section Reusing existing aspects mentioned a set of existing AC that can be reused to implement distributed programming. This section introduces the notion of distributed pointcuts.
A distributed pointcut is a pointcut that is composed of joinpoints located
on different hosts. We already mentioned in section
Aspect components that a pointcut is
defined with 3 expressions (object, class and method pointcut expressions). A
4th one is added for distributed pointcuts: host pointcut expression.
This is a regular expression on host names. The naming schema depends on the
remote communication protocol. When RMI is used these name are RMI URL as
registered in the RMI registry, i.e. names such as rmi://aHost.com/aNameForAnObject.
Different signatures of the
pointcut method
are available to take into account this additional parameter in pointcut
definitions.
Distribution with JAC comes with a set of additional features: classes can be
remotely uploaded on remote sites and objects can be remotely instantiated.
AC instances are replicated on hosts declared in the jac.topology
property (defined in the application descriptor) and kept in synchronization:
each modification performed on one replica is automatically propagated on the
others. By this way, all hosts access the same defintions of AC and pointcuts.
When sevaral AC are woven around the same joinpoint, the executing order
needs to be defined. This order needs to be specified by the AC developper with
the jac.comp.wrappingOrder property of the application descriptor.
This property specifies the global order of wrapper classes for all the
joinpoints of the application (see the jac.prop file for an example
of such property).
When several AC are woven around the same joinpoint, they form a chain and
are executed in the order specified in jac.comp.wrappingOrder. The
following figure illustrates the call stack for a chain of 3 wrappers.