My experiments with dozer
Introduction
The object to object mapping activity happens to
constitute a significant part during a front tier services hand-off of a
request to the down-stream layers (be it DAO or another service). The major
overhaul comes across as a substantial pain of using naive source getters and
target setters in building the target objects of interest and confounds the
developer and the maintainer alike. The often repeated transformations (during
the mapping) such as formatting, composing, splitting, target attribute/field
substitution are much left to the user's wisdom to handle in some non-descript
isolated pockets of code. An ubiquitous example could be a phone number, or an
address where different flavors of structures exist to cater to different locale. For instance
in one flavour the road and the block name/numbers are captured in fields
called address1 and address2 while an another flavor refers the same as
street1/line1 and street2/line2 respectively. Further a state field in the
address object can be referred to as province and zip can be substituted for
pin.
Intent
The intent of reminiscing on this article and
the work underneath it came about when this above stated problem started
quickly getting out of hand in a recent situation at work.It was when the
number of different objects to be mapped to one or the other type started to
exceed beyond handful (say more than 25) where the semantics of the source and
target objects remained same however the field names started out to be
different(just like address1 and street1). It was an occasion where a classic
remote proprietary service needed to pass a request object to a target type
with many attributes synonym-ed to a different names.
The first thing that crossed my mind was to keep
off from the lazy habit of jumping on the bandwagon of getters and
setters(these days usually any half-good POJOs comes at-least with the gets,
sets and default no-arg constructors without much effort). Ofcourse I am not
whining at all on the POJO principles here; but just felt that life is too
short for coding with POJO gets/sets and thus desired to have a bit more atop
that can take care of mundane stuff in a declarative fashion than to just code.
Infact this is the theme that is closer to my heart in embracing the project
lombok annotations.
The real intent / reason on this article is by
no means a rant on bean-bean mappers but as is explained further in the use
cases section it is more about a few special situations that called in for a
slightly general solution to be built forming the crux of this article. Please
read further to know more on this.
Why dozer
Thus dawned a bright occasion on a winter
morning of November 2014; where we decided to do some soul searching and
figure out whats the best tool that is worth its salt for fitting the purpose
we had. Tools / libraries such as commons convert from apache commons and
spring libraries were looked upon as first choices for those libraries usually
form the motherboard of many of the projects that we build. While the main aim
of this transformation was about developer simplicity in not having to code and
keep it mostly declarative; the approach was not desired to be entirely bereft
of the coding.
So while commons / spring approaches needs still
a fair amount of hand cranked code for every conversion type; the search
continued on beyond the comfort zone of apache commons, spring . The web has
many articles of comparison on bean-bean mapping and thus we will not go
about comparing one library against another here. However on one such occasion
when we chanced upon dozer; it sounded promising with its impressive feature
set such as declarative xmlized way of defining the mapping and a great hook
for plugging in custom converters that can be sewn together by using spring
beans. After a quick check within organization for the yearned buy in; we
quickly set out to find out the various bells and whistles it offered.
Use-cases
Most of the mundane mappings are handled by
simplistic sensible defaults applied by dozer configurations. For
instance the declarative xml pieces for a mapping did'nt needed any explicit
mention of fields that are commonly available in source and target classes.
Readers are encouraged to refer web articles on dozer to glean information. However, there were
some peculiar use-cases that were frequented by us that couldn't be found in
either dozer documentation or web and essentially are the reasons for opening
this blog post.
These specific use cases were usually related to
1. An immutable/constant object or value to be
substituted to a target field
2. In another case ; the target objects had one
nagging pattern in that many of the fields such as address, phone were always
requiring to be of list type(POJO
generators for these third party target objects were designed to emit these
fields as list and hence we had no choice). This usually meant such target fields not only had to be mapped
but also need to be encapsulated to a list.
These specific use cases that needed building
different custom converters purposed for as follows:
1. A field of target class that needs to be
substituted by a type specific constant (mostly the primitives and date). Here
the particular target field is simply substituted with a constant.
2. A field of target class that needs to be
substituted by a pre-defined bean in the spring beans xml. Here the particular
target field is a complex custom type which is a bean defined with appropriate
values for its attributes.
3. A target field that needs to be populated as
java collection type (for now List type is only supported). Here the source
bean needs to be converted to a target type and further added as an element
into a collection of the target type.
Implementation
A custom converter in dozer provides the
following configurable keys (amongst many others) which could be utilized for
the purpose at hand.
1. custom-converter-id : This is bean id straight
out of a bean definition in the spring based beans.xml that is used in the
project.
2. custom-converter-param : This is basically a
helpful hint or a string parameter which is set post construction of the
converter
A first implementation of the primitive
converters, pre-defined bean substitutors and a source to target list mapper
can be found in my github repository : dozer-experiments.
The first step in before building up the
solution was to firm up the project based on spring and get up quickly with a
workable beans.xml for the purpose.
Note:The rationale for adding beans.xml and even
dozer bean configuration files to src/test/resources and primarily making test
package as the go-to source for understanding is to keep the actual main
classes light since the usage and configuration of converters can become very
contextual. Basically the beans or dozer files primarily are data driven and
are best served with test data in a demo environment rather than making them as
part of main jar file. The readers are encouraged to understand README, test package, configuration samples and accordingly make up
their configurations.
Normally the spring based project i write would
usually require some quintessentials listed below.
1. Need my beans xml to be sleak and hence require spring p-namespace and
c-namespace.These allow p:<property-name> and c:<attribute-name> in
the bean definition as an xml attribute.(half sanity may survive just because
of this itself :) - as
otherwise beans.xml too busy!).
2. Spring Annotation Processors(@PostConstruct etc)
are usually made use of along with a few defalt values used from
bean-default.properties so the following snippet.(I dont like repeating package names or base
path names which increases the breadth and/or height of xml)
3. Using Slf4j logger (Please refer to logback.xml for log level and formats) and dozer related configurations in the beans.xml as follows.
<bean
|
|
class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"
|
|
scope="singleton"
/>
|
|
<bean
|
|
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
|
|
scope="singleton">
|
|
<description>This
is a common properties file that may declare
|
|
repetatively usable strings, constants and class/package
names etc
|
|
</description>
|
|
<property name="location"
value="bean-default.properties"
/>
|
|
</bean>
<bean id="system-properties-setup" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" p:targetObject="#{@systemProperties}" p:targetMethod="putAll"> <property name="arguments"> <util:properties> <!-- <prop key="java.util.logging.SimpleFormatter.format">%1$tF %1$tT [%4$s]: %5$s %n</prop> --> <prop key="dozer.configuration">dozer.properties</prop> <prop key="dozer.debug">false</prop> </util:properties> </property> </bean> |
1. Converters of primitive type: These deal with turning a string parsable
value to a specific primitive type such as int, long, short etc. It can also
support for date today with an assumed simple date format. These as it sounds
are going to substitute a value specified in (as part of dozerBeanMapper.xml) the attribute custom-converter-param. The
converter itself is as specified in custom-converter-id. Please refer
to README for details.
2. Converters to substitute a pre-defined target
bean : A fully defined
target bean can act as a stand in while mapping some fields.
3. A source to target list converter: It is self explanatory.
4. The dozer mapper definition is completely done in beans xml. so its
imperative to use this than create some default way for the mapper.
Most of the bean ids have self explanatory
meaning and hence will not be explaining all beans here.
While there exists a good reference on bean
mapping within the dozer user guide; here an attempt is made with a small
snippets xml to explain the solution for the use cases addressed. The example
objects/classes for conversion are declared in dozer bean mapping file housed in
the examples package for demonstration purpose.
For instance ;a quick way to get up with an use
of substitution of constant values is as follows; where the
custom-converter-param has the actual value to be converted to desired
primitive type.
<mapping
map-id="source-to-target-with-few-constants">
<class-a>com.github.venkateshamurthy.dozer.converters.examples.SourceHolder</class-a>
<class-b>com.github.venkateshamurthy.dozer.converters.examples.TargetHolder</class-b>
<field
map-id="source-to-target"><a>source</a><b>target</b></field>
<!-- the
custom-converter-ids are primitive converters but valueas are strings to be
converted -->
<field
custom-converter-id="to-date"
custom-converter-param="1947-08-15"><a>dob</a><b>dob</b></field>
<field
custom-converter-id="to-int"
custom-converter-param="24"><a>age</a><b>age</b></field>
<field custom-converter-id="to-double"
custom-converter-param="500000.0"><a>salary</a><b>salary</b></field>
<field
custom-converter-id="to-long"
custom-converter-param="100300500700"><a>uan</a><b>uan</b></field>
</mapping>
Where as a pre-defined bean substitution can be
had by using the following snippet:
<mapping map-id="source-to-substitute-target">
<class-a>com.github.venkateshamurthy.dozer.converters.examples.SourceHolder</class-a>
<class-b>com.github.venkateshamurthy.dozer.converters.examples.TargetHolder</class-b>
<!-- the
custom-converter-param values are prefixed here with bean:bean-name as the
target bean for substituting must be defined beans.xml -->
<field
custom-converter-id="source-to-target-pre-defined" custom-converter-param="bean:pre-defined-target">
<a>source</a>
<b>target</b>
</field>
</mapping>
Note: The custom-converter-param has a mandatory
prefix bean: and suffixed with an actual bean id from
the bean xml representing the pre-defined target bean. This structural
constraint of prefix bean: is mandatory and is a personal
convention
The other type is about source to target list
type and requires a proper mapping to exist between a singular element of
source class to target class. The snippet as follows as an example:
<mapping map-id="source-to-target-list" >
<class-a>com.github.venkateshamurthy.dozer.converters.examples.SourceHolder</class-a>
<class-b>com.github.venkateshamurthy.dozer.converters.examples.TargetListHolder</class-b>
<!-- the converter is
source to a target list and custom-converter-param values are prefixed here
with map-id: as the singular element mapping is required in dozer bean mapping
definition -->
<field
custom-converter-param="map-id:source-to-target" custom-converter-id="source-to-target-list">
<a>source</a> <b>targets</b>
</field>
</mapping>
Note: The custom-converter-param has a
prefix map-id: and suffixed with an actual mapping id from the
dozerBeanMapping xml (or some included dozer mapping config xml file). This
basically makes sure to apply the correct mapping between singular element of
source and target class and then subsequently return a list wrapping/including
the singular target element. This structural constraint of prefix map-id: is
mandatory and is a personal convention.
Usage:
In this section a few test code snippets are
mentioned as follows. The reader can take que from this in order to make use of
the new converters proposed here.
/** test a predefined target substitution */
@Test
public void
testPreDefinedTargetSubstitution() throws NoSuchFieldException,
SecurityException {
SourceHolder sourceHolder
= ctx.getBean("source-holder",
SourceHolder.class);
TargetHolder targetHolder
= mapper.map(sourceHolder,
TargetHolder.class,
"source-to-substitute-target");
Target expectedTarget =
ctx.getBean("pre-defined-target", Target.class);
assertEquals(expectedTarget, targetHolder.getTarget());
}
/** test a field (of type
date/int/long etc) for a constant substitution */
@Test
public void
testTargetWithFewConstants() {
SourceHolder sourceHolder
= ctx.getBean("source-holder",
SourceHolder.class);
TargetHolder targetHolder
= mapper.map(sourceHolder,
TargetHolder.class, "source-to-target-with-few-constants");
log.info(targetHolder.toString());
}
/** test a singular element
to a destination list type */
@Test
public void
testTargetList() {
SourceHolder sourceHolder
= ctx.getBean("source-holder",
SourceHolder.class);
Source source =
sourceHolder.getSource();
TargetListHolder
targetHolder = mapper.map(sourceHolder,
TargetListHolder.class,
"source-to-target-list");
List<Target> targets
= targetHolder.getTargets();
assertFalse(targets.isEmpty());
assertTrue(targets.size()
== 1);
Target target =
targets.get(0);
assertEquals(source.getAddress1(), target.getStreet1());
assertEquals(source.getAddress2(), target.getStreet2());
assertEquals(source.getPhone(), target.getPhone());
log.info(targetHolder.getTargets().toString());
}
While test code helps in understanding the
usage; a good look at the actual converter classes in the main package helps
deepen the understanding.
Summary:
In summary, though a set of peculiar use cases
of bean to bean mapping are discussed within a context, it is largely felt that
the general solution discussed here seems to be useful in a variety of similar
situations arising in different contexts. This set of converters are set only
to grow with different levels of customizations /enhancements applied to it .
Thus I request earnestly all the readers to express their opinion/reviews on
this to make this article and code a better experience for the users.
No comments:
Post a Comment