Section 1. |
|
Section 2. |
|
Section 3. |
|
Section 3.1 |
|
Section 3.2 |
|
Section 4. |
|
Section 5. |
|
Section 5.1 |
|
Section 5.2 |
|
Section 6. |
|
Section 6.1 |
|
Section 6.2 |
|
Section 7. |
|
Section 7.1 |
|
Section 7.2 |
|
Section 8. |
|
Section 8.1 |
|
Section 8.2 |
|
Section 8.3 |
|
Appendix A. |
|
Appendix B. |
Lima-Loa is an automated implementation of the Gang of Four Adapter Design Pattern, for Java. It aims to greatly reduce or get rid of the need to write dull boiler plate code when creating an adapter.
Lima-Loa is Open Source software, published under the Apache License, version 2.0.
The Gang of Four Design Patterns book describes the intent of the Adapter pattern as follows:
Convert the interface of a class into another interface interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
One implementation of the adapter pattern (class adapters) involves multiple inheritance, so is not possible using the Java programming language. Another implementation (object adapters) involves object composition, and is the typical solution when using Java. The following UML diagram shows the participants in the object adapter implementation.
The participants are as follows:
Client - calls methods on the source interface. Is unaware that an adapter is even in use.
Source - the interface the client calls, that most likely uses concepts familiar to the client (i.e. the client's domain).
Adapter - adapts method calls from the source interface to the target object.
Target - the object being adapted, i.e. the adaptee.
In the above diagram, the client calls methodA(int}
on the adapter, which then triggers the adapter to call
methodB(long)
on the target object. The adapter then passes the result of the target method call back to the
client. In order to do this, the adapter must convert the int
parameter to a long
, then the
boolean
result to a String
.
Typically an object adapter would need to be manually written by a developer. In the above simple example that would not be particularly difficult, but in a more complex example where the parameters and results are large graphs of Java objects (e.g. custom JavaBeans) and where exceptions must also be mapped, a reasonably large amount of dull yet brittle code would need to be manually written.
Lima-loa is an automated implementation of an object adapter, and intends to replace large amounts of such dull yet brittle code with a relatively small amount of configuration.
Lima-Loa is a pure Java library, and requires JDK 1.5 or above. It is tested with recent releases of the Sun 1.5 and 1.6 JDKs.
Lima-Loa is designed to work with Dozer version 5.x, and Spring versions 2.0.x, 2.5.x and 3.0.x. It is tested with the following releases, but other compatible versions should also work.
Dependency |
API Used |
Version Tested |
Dozer |
5.x |
5.2 |
Spring |
2.0.x |
2.0.8 |
2.5.x |
2.5.SEC01 |
|
3.0.x |
3.0.1 |
Lima-Loa requires the following list of third party Java libraries in order to work. One is a direct dependency, the rest are dependencies of Dozer or Spring. For ease these are included in the "lib" folder of the distribution, with the exception of Spring (due to the size). Other versions or combinations of these libraries may well work, they simply haven't been tested.
If only using the Direct API:
Not using Dozer |
Using Dozer 5.x |
commons-logging-1.1.1.jar |
dozer-5.x.jar |
If using the Spring Integration:
Not using Dozer |
Using Dozer 5.x |
|
Spring 2.0.x |
spring-core-2.0.x.jar |
spring-core-2.0.x.jar |
Spring 2.5.x |
spring-core-2.5.x.jar |
spring-core-2.5.x.jar |
Spring 3.0.x |
org.springframework.asm-3.0.x.jar |
org.springframework.asm-3.0.x.jar |
There are two ways to use Lima-Loa, both have exactly the same functionality:
Direct API - slightly more effort and creates a compile-time dependency but is great if you want a pure Java solution.
Spring Integration - very simple, keeps your application loosely coupled, and is great if you are using the Spring Framework.
Firstly, create an instance of AdapterFactory by calling one of the following methods on the org.limaloa.AdapterFactory class:
public static AdapterFactory getInstance() public static AdapterFactory getInstance(List<String> mappingFiles, boolean useDozer)
The first method uses the default settings, which include not to use Dozer. These factory instances are relatively expensive objects and are intended to be re-used, if you need to create more than one adapter object.
Next, define a List of MethodMappings. See Section 6 for details of the various options available, but as long as you are not using overloaded methods then one of the following constructors can be used to create each MethodMapping instance:
public MethodMapping(String source, String target) public MethodMapping(String source, String target, List<ExceptionMapping> exceptionMappings)
Where source is the source method name, target is the target method name, and the optional exceptionMappings convert exceptions thrown by the target method.
Next, create an adapter object by calling the following method on the factory instance:
public <T> T createAdapter(Class<T> sourceInterface, Object target, List<MethodMapping> methodMappings) throws MappingCreationConfigurationException
This returns an object that implements sourceInterface, and no explicit cast is needed. The target is the object you'd like the adapter method calls to be redirected to, i.e. the object being adapted. The List of methodMappings is as created in the previous step. At runtime, all calls to the adapter are seamlessly redirected to the nominated method in the target object.
An exception is thrown if there is anything wrong with the specified configuration. As much as possible is checked at creation time, rather than at runtime.
See the "direct-api/simple" example in the "samples" folder of the distribution for a fully working example. See the ReadMe.txt file in that folder for details of the example.
Lima-Loa is a Spring extension, so you simply add it to the classpath and use a new schema in your Spring Application Context xml files. Under the covers, the Lima-Loa Spring Integration simply calls the Direct API, so the functionality is identical.
Firstly, add the Lima-Loa schema to your Application Context xml file:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:ll="http://www.limaloa.org/schema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.limaloa.org/schema http://www.limaloa.org/schema/limaloa-0.6.xsd">
This should mean you get content-assist and element/attribute descriptions if you use a Schema-aware XML editor (e.g. Eclipse).
The next step is to create an Adapter Configuration (which corresponds to a Direct API AdapterFactory) as follows:
<ll:adapterConfiguration/>
Note that unless you need to use Dozer then this step is actually optional - if no adapterConfiguration is defined then a default one will be created on demand. Note also that only one adapterConfiguration is allowed per Application Context (this includes any parent xml files in a hierarchical Application Context), so if more than one is found then an exception is thrown.
If you would like to use Dozer and specify some mapping files, the syntax is as follows:
<ll:adapterConfiguration useDozer="true"> <ll:configFiles> <ll:file>custom-bean-mapping1.xml</ll:file> <ll:file>custom-bean-mapping2.xml</ll:file> </ll:configFiles> </ll:adapterConfiguration>
Next, create an adapter as a Spring Bean. See Section 6 for details of the various options available, but as long as you are not using overloaded methods then the following XML elements (known as the short syntax) can be used:
<ll:adapter id="source" interface="source" targetObject="target"> <ll:methodMapping source="sourceMethod1" target="targetMethod1"/> <ll:methodMapping source="sourceMethod2" target="targetMethod2"/> </ll:adapter>
This creates a Spring Bean that implements interface. The targetObject is a reference to another Spring bean you'd like any adapter method calls to be redirected to, i.e. the object being adapted. The methodMappings map source interface method names to target object method names. At runtime, all calls to the adapter are seamlessly redirected to the nominated method in the target object.
The adapter is a bean instance that can be used just as any other Spring bean, for example it can be injected as a property into another object. That other object need not know that Lima-Loa is even being used, as its only dependency is on the source interface.
As with the Direct API, an exception is thrown if there is anything wrong with the specified configuration. As much as possible is checked at creation time, rather than at runtime.
See the "spring/simple" example in the "samples" folder of the distribution for a fully working example. See the ReadMe.txt file in that folder for details of the example.
Method mappings are more complete in this release, but static methods are still not supported supported. There are two ways of identifying methods:
Note that each method mapping must use the same type of syntax to identify the source and target methods, you cannot mix the shorter and longer syntax for a single method mapping.
To use the shorter syntax, create the MethodMapping using one the following constructors:
public MethodMapping(String source, String target) public MethodMapping(String source, String target, List<ExceptionMapping> exceptionMappings)
To use the longer syntax, first create a Parameter instance for each of the source and target method parameters using one the following constructors:
public Parameter(Class<?> parameterClass) public Parameter(String type) throws ClassNotFoundException
Passing the type as a String allows you to sepcify primitive parameters by type name (i.e. boolean, byte, short, int, long, float, double, or char). If not one of these primitive type names, Lima-Loa will assume the String is a fully-qualified classname and attempt to instantiate the class using Class.forName(type).
Next, put the parameters in Lists and create the source and target Method instances using the following constructor:
public Method(String name, List<Parameter> parameters)
If the method doesn't take any parameters (i.e. it is void) then pass a null or empty list of parameters.
Lastly, create the MethodMapping using using one the following constructors:
public MethodMapping(Method source, Method target) public MethodMapping(Method source, Method target, List<ExceptionMapping> exceptionMappings)
To use the shorter syntax, use the source and target attributes of the methodMapping XML element, as follows:
<ll:adapter id="source" interface="source" targetObject="target"> <ll:methodMapping source="sourceMethod" target="targetMethod"/> </ll:adapter>
To use the longer syntax, use the source and target XML elements, with nested parameter XML elements, as follows:
<ll:adapter id="source" interface="source" targetObject="target"> <ll:methodMapping> <source name="sourceMethod"> <parameter type="java.lang.String"/> <parameter type="long"/> </source> <target name="targetMethod"> <parameter type="int"/> <parameter type="boolean"/> </target> </methodMapping> </ll:adapter>
The parameter type is passed to the Direct API as a String, which allows sepcifying primitive parameters by type name (i.e. boolean, byte, short, int, long, float, double, or char). If not one of these primitive type names, Lima-Loa will assume the String is a fully-qualified classname and attempt to instantiate the class using Class.forName(type).
If the method doesn't take any parameters (i.e. it is void) then simply ommit any parameter XML elements, as follows:
<ll:adapter id="source" interface="source" targetObject="target"> <ll:methodMapping> <source name="sourceMethod"/> <target name="targetMethod"/> </methodMapping> </ll:adapter>
Mapping of "basic" types is handled directly by a built-in mapper, and anything else is delegated to Dozer (if enabled) - which is an Open Source library intended for mapping custom JavaBeans.
The built-in mappings handle the following situations:
Destination | ||||||||||
boolean Boolean |
byte Byte |
short Short |
int Integer |
long Long |
float Float |
double Double |
char Character |
String | ||
Source | boolean Boolean |
Yes | No | No | No | No | No | No | No | Yes |
byte Byte |
No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | |
short Short |
No | Yes*5 | Yes | Yes | Yes | Yes | Yes | Yes | Yes | |
int Integer |
No | Yes*5 | Yes*5 | Yes | Yes | Yes | Yes | Yes*5 | Yes | |
long Long |
No | Yes*5 | Yes*5 | Yes*5 | Yes | Yes | Yes | Yes*5 | Yes | |
float Float |
No | No | No | No | No | Yes | Yes | No | Yes | |
double Double |
No | No | No | No | No | No | Yes | No | Yes | |
char Character |
No | Yes*5 | Yes*3 | Yes*3 | Yes*3 | Yes*3 | Yes*3 | Yes | Yes | |
String | Yes *1 | Yes*2 | Yes*2 | Yes*2 | Yes*2 | Yes*2 | Yes*2 | Yes*4 | Yes |
Any use of unsupported combinations (marked as "No" in the table above) results in a
org.limaloa.object.UnsupportedObjectMappingException
thrown
at runtime.
*1 - Treats "true" (case-insensitive) as true, anything else (including null) as "false",
using Boolean.valueOf(String)
.
*2 - Uses the appropriate Wrapper.valueOf(String)
method, which throws
java.lang.NumberFormatException
if invalid, null or empty.
*3 - Converts the raw 16-bit character value to a number, for example 'A' becomes 65.
*4 - Expects single character length Strings, org.limaloa.object.UnsupportedObjectMappingException
thrown
at runtime if the String is empty or length is more than 1.
*5 - Checks the numeric value is not too large or too negative to fit into the destination
type, org.limaloa.object.InvalidNumericMappingException
thrown
at runtime if the value cannot fit into the destination type. For example, if converting from
a short
to byte
, the source value must be from -128 to +127.
Dozer (http://dozer.sourceforge.net/)
is an object mapper for custom JavaBeans. It expects objects to follow the JavaBean
conventions (have a no arguments constructor, and have getter and setter methods
for each attribute), and can handle "graphs" of nested objects of any
depth. By default it will map attributes in any source type to attributes
in any destination type by attribute name, with any attributes that don't have a matching
name in the destination object being left at null
. To define attribute
mappings when the attribute name differs, or the object graph structure itself differs, you
simply define a Dozer XML mapping file.
When using Dozer within Lima-Loa, XML mapping files are defined in the parameter
to the org.limaloa.AdapterFactory getInstance(List mappingFiles)
method (if using the Direct API), or are defined by <configFiles>
XML elements
within the <adapterConfiguration>
XML element (if using the Spring Integration).
See the "direct-api/custom-bean" example in the "samples" folder of the distribution for a fully working example of using Dozer with Lima-Loa via the Direct API. See the ReadMe.txt file in that folder for details of the example.
See the "spring/custom-bean" example in the "samples" folder of the distribution for a fully working example of using Dozer with Lima-Loa via Spring Integration. See the ReadMe.txt file in that folder for details of the example.
Any exceptions (checked or unchecked) thrown by the target method can be caught and mapped to an alternative exception that will then be propogated back to the calling client code. Any exceptions thrown by a target method that don't have any explicit mapping defined will be handled by Lima-Loa as follows:
org.limaloa.mapping.method.InvalidMethodInvocationException
,
with the original exception in the cause.
For each exception to map, create an ExceptionMapping instance using one of the following constructors:
public ExceptionMapping(String sourceClassname, String targetClassname) public ExceptionMapping(Class<? extends Throwable> sourceClass, Class<? extends Throwable> targetClass) public ExceptionMapping(String sourceClassname, String targetClassname, boolean wrap) public ExceptionMapping(Class<? extends Throwable> sourceClass, Class<? extends Throwable> targetClass, boolean wrap)
The sourceClassname or sourceClass is the exception thrown by the source method, and the targetClassname or targetClass is the exception thrown by the target method, i.e. the mapping is from target back to source. If defining exceptions as a String, Lima-Loa will assume it is a fully-qualified classname and attempt to instantiate it using Class.forName(classname). The wrap parameter indicates whether or not to pass the original exception as the cause in the mapped exception, by default Lima-Loa will do this.
Next, add all of your ExceptionMapping instances into a List. At runtime, exceptions thrown by the target method will be mapped by comparing whether it is the same type or a subtype, in the order defined by the list. This means that if using both base types and sub types, the sub types must come earlier in the list otherwise the base type mapping will match instead. This is deliberately identical to the constraints of ordering catch blocks in Java code.
If using the shorter syntax, pass the List of ExceptionMappings using the following constructor:
public MethodMapping(String source, String target, List<ExceptionMapping> exceptionMappings)
If using the longer syntax, pass the List of ExceptionMappings using the following constructor:
public MethodMapping(Method source, Method target, List<ExceptionMapping> exceptionMappings)
Regardless of whether using the shorter or longer syntax, simply add any number of exception XML elements as appropriate as follows:
<ll:adapter id="source" interface="source" targetObject="target"> <!-- shorter syntax --> <ll:methodMapping source="sourceMethod1" target="targetMethod2"> <ll:exception source="sourceEx1" target="sourceEx1"/> </ll:methodMapping> <!-- longer syntax --> <ll:methodMapping> <source name="sourceMethod2"/> <target name="targetMethod2"/> <ll:exception source="sourceEx2" target="sourceEx2" wrap="false"/> <ll:exception source="sourceEx3" target="sourceEx3"/> </methodMapping> </ll:adapter>
The source attribute is the fully-qualified classname of the exception thrown by the source method, and the target is the fully-qualified classname of the exception thrown by the target method, i.e. the mapping is from target back to source. The optional wrap attribute indicates whether or not to pass the original exception as the cause in the mapped exception, the default is true.
At runtime, exceptions thrown by the target method will be mapped by comparing whether it is the same type or a subtype, in the order that the exception XML elements are defined. This means that if using both base types and sub types, the sub types must be defined before base types otherwise the base type mapping will match instead. This is deliberately identical to the constraints of ordering catch blocks in Java code.
When mapping exceptions and instantiating the source exceptions, Lima-Loa chooses which constructor to use by using the following prioritised list:
If wrapping the original target exception is enabled (the default):
If wrapping the original target exception is disabled:
If Lima-Loa cannot find any appropriate constructors at all, it will throw a org.limaloa.mapping.exception.ExceptionMappingConfigurationException upon startup.
Copyright 2009 Chris Nappin
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Lima-Loa uses the Jakarta Commons Logging (JCL) library for runtime logging, as does the Spring framework itself. JCL can be configured to log in a variety of ways, see http://commons.apache.org/logging for further details.
Lima-Loa uses log levels as follows: