A POJO to POJO Java Mapper
Brought to you by:revittorio76
- Summary
- Files
- Reviews
- Support
- Wiki
- Code
Menu▾▴
- Wiki Home
- Browse Pages
- Browse Labels
- Formatting Help
Examples
Hello-world Sample
In order to explain how to use JMapper, let's start with a very simply example.
Suppose you have a project with 2 layer: Middleware and Front-end.
For each layer you want to define a specific object with layer-related characteristics; for example, front-end object have all fields represented as String in order to facilitate the visualization of the data but in middleware you want to use correct formats in order to manipulate data correctly.
(For this example we do not considerate how data is stored; it's simply another layer and mapping could work in the same way).
Suppose we are manipulating a Person, so we could have 2 person objects, one for MW and one for FE:
@Mappingpublic class PersonMW { private long code; private String surname; private String name; private Gender gender; private Date birthDate; //.... constructor - getter/setter - toString - hashCode/equals standard methods}public enum Gender { Male, Female;}
and
public class PersonFE { private String code; private String surname; private String name; private String gender; private String birthDate; //.... constructor - getter/setter - toString - hashCode/equals standard methods}
NOTE that there is ONLY one annotation in PersonMW object.
You can map from PersonMW to PersonFE and vice versa with this only row:
session.map(personFe, PersonMW.class);
Following a JUnit sample to test both conversions:
@Test public void testPerson() { PersonMW personMw = personService.getPerson(1); Assert.assertNotNull(personMw); System.out.println("testPerson: personMw="+personMw); PersonFE personFe; try { personFe = MapperUtils.Instance.getSession().map(personMw, PersonFE.class); } catch (JMapperConversionException e) { e.printStackTrace(); throw new RuntimeException("Mapping error 1", e); } Assert.assertNotNull(personFe); System.out.println("testPerson: personFe="+personFe); Assert.assertEquals(personMw.getGender().name(), personFe.getGender()); PersonMW personMw2; try { personMw2 = MapperUtils.Instance.getSession().map(personFe, PersonMW.class); } catch (JMapperConversionException e) { e.printStackTrace(); throw new RuntimeException("Mapping error 2", e); } Assert.assertNotNull(personMw2); System.out.println("testPerson: personMw2="+personMw2); Assert.assertEquals(personMw,personMw2); }
The meaning of the annotation at the beginning of PersonMW class is:
enable mapping of PersonMW objects from any kind of object and to any kind of object, mapping all fields one-by-one, searching corresponding fields in opposite class by name and converting them using default primitive conversions.
The result in JUnit test is this one:
suppose that personService implementation is something like following:
public class PersonService { public PersonMW getPerson(long code){ return new PersonMW( code, "Obama", "Barack", Gender.Male, buildDate(4, 8, 1961, 15, 37, 45)); } private Date buildDate(int day, int month, int year, int hour, int minute, int second) { //... build a java.util.Date object }}
the test will write in stdout following rows:
testPerson: personMw=PersonMW [code=1, surname=Obama, name=Barack, gender=Male, birthDate=Tue Nov 12 15:37:45 CET 9]testPerson: personFe=PersonFE [code=1, surname=Obama, name=Barack, gender=Male, birthDate=12/11/0009 15:37:45]testPerson: personMw2=PersonMW [code=1, surname=Obama, name=Barack, gender=Male, birthDate=Tue Nov 12 15:37:45 CET 9]
A bit complex Sample
Let's try to map a more complex structure.
Suppose we have a person object like this:
@Mapping(source=PersonFE_3.class, target=PersonFE_3.class)public class PersonMW_3 { private long code; private String surname; private String name; private Gender_2 gender; @MappingField(mapper="Full date format") private Date birthDate; @MappingField(mapper="Short date format") private Date registrationDate; @FieldMappings({ @MappingField(direction=MappingDirection.Outgoing, value="lastLoginYear", mapper="Format date", param="yyyy"), @MappingField(direction=MappingDirection.Outgoing, value="lastLoginMonth", mapper="Format date", param="MM"), @MappingField(direction=MappingDirection.Outgoing, value="lastLoginDay", mapper="Format date", param="dd"), @MappingField(direction=MappingDirection.Incoming, value="this", mapper="Build Date", param={"lastLoginYear","yyyy","lastLoginMonth","MM","lastLoginDay","dd"}) }) private Date lastLoginDate; @FieldMappings({ @MappingField(direction=MappingDirection.Outgoing, param={"java.util.ArrayList","test.jmapper.helloworld.structures.fe.AddressFE_3"}), @MappingField(direction=MappingDirection.Incoming, param={"java.util.HashSet","test.jmapper.helloworld.structures.mw.AddressMW_3"}) }) private Set<AddressMW_3> addresses; @NotMapped private Date objectCreationDate = new Date(); //.... constructor - getter/setter - toString - hashCode/equals standard methods}@Mapping(source=Integer.class, target=Integer.class, mapNotAnnotatedFields=false)public enum Gender_2 { Male(1), Female(2); private final int code; private Gender_2(int code) { this.code = code; } @ConversionMethod public Integer getCode(){ return code; } @BuildMethod public static Gender_2 fromCode(Integer code){ for (Gender_2 elem : Gender_2.values()) { if(elem.getCode()==code) return elem; } throw new RuntimeException("Unknown code "+code); }}@Mappingpublic class AddressMW_3 { private long code; private String city; private String street; private int number; //.... constructor - getter/setter - toString - hashCode/equals standard methods}
and corresponding FE objects
public class PersonFE_3 { private Long code; private String surname; private String name; private Integer gender; private String birthDate; private String registrationDate; private String lastLoginYear; private String lastLoginMonth; private String lastLoginDay; private List<AddressFE_3> addresses; //.... constructor - getter/setter - toString - hashCode/equals standard methods}public class AddressFE_3 { private Long code; private String city; private String street; private String number; //.... constructor - getter/setter - toString - hashCode/equals standard methods}
NOTE all the annotations in all MW objects
Mapping code to switch from PersonMW_3 and PersonFE_3 and vice versa will be
session.map(personFe, PersonMW_3.class);
that is exacly the same as above.
The only things that changes are mapping annotations; used annotations have following meanings:
- @Mapping: in this example this annotation allows mapping only between PersonMW_3 and PersonFE_3 kind of objects (in previous example it was from PersonMW to any kind of Objects)
- @MappingField(mapper="Full date format"): use a specific mapper to map this date (not the default one). This mapping have tha name "Full date format".
- @FieldMappings: specify a list of @MappingField, all used with the same field; this annotation is used when a field is mapped in more that one fields in other class or when incoming mapping differs from outgoing mapping (see addresses field)
- @MappingField(direction=MappingDirection.Outgoing, value="lastLoginYear", mapper="Format date", param="yyyy"): attribute means are:
- direction: specify mapping direction; in this case mapping is used only in OUTGOING mapping (from PersonMW_3 to PersonFE_3)
- value: corresponding remote field name (different from current one, used by default)
- mapper: mapper name used to map this field to specified remote one
- param: parameters passed to specified mapper
- @NotMapped: this field won't be mapped
- @ConversionMethod: indicate that this method will be use to instantiate remote class (insthead of constructor)
- @BuildMethod: indicate that this method will be use to instantiate current class (insthead of constructor)
To complete mapping there are something missing: specific mapping used in these classes.
In above example, we have used following specific mapping:
- Full date format
- Short date format
- Format date
- Build Date
These are specific mapping so we have to define them.
We will define all of them as Converter methods inside a Converter Class:
@Converterpublic class DateConverter implements Serializable { private static final long serialVersionUID = -1381822225120226227L; private static final String DATE_SEPARATOR = "/"; private static final DateFormat SHORT_CONVERTER = new SimpleDateFormat("dd/MM/yyyy"); private static final DateFormat FULL_CONVERTER = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss.SSS"); @ConverterMethod("Short date format") public Date fromStringShort(String value) throws ParseException { return SHORT_CONVERTER.parse(value); } @ConverterMethod("Short date format") public String toStringShort(Date value) { return SHORT_CONVERTER.format(value); } @ConverterMethod("Full date format") public Date fromStringFull(String value) throws ParseException { return FULL_CONVERTER.parse(value); } @ConverterMethod("Full date format") public String toStringFull(Date value) { return FULL_CONVERTER.format(value); } /** * Require one single parameter with the format of the return value * Eg.: "yyyy" or "MM/yyyy" * * @param value date to convert * @param param format of the return value (see SimpleDateFormat) * @return formatted date * @see java.text.SimpleDateFormat */ @ConverterMethod("Format date") public String getDateField(Date value, String[] param) { SimpleDateFormat format = new SimpleDateFormat(param[0]); return format.format(value); } /** * Require pairs of parameters with, for each pair, following values: * - name of the field/method in input object to use * - format of the part of date corresponding to the field * Eg.: "year","yyyy","month","MM","day","dd" * * @param o object containing field to convert * @param param parameters of conversion * @return converted Date * @throws ParseException * @see java.text.SimpleDateFormat */ @ConverterMethod(value="Build Date", allowSubClasses=AllowSubClasses.Source) public Date buildDate(Object o, String[] param) throws ParseException { if(param.length>1) { StringBuilder sourceBuilder = new StringBuilder(getValue(o, param[0]).toString()); StringBuilder formatBuilder = new StringBuilder(param[1]); for(int i=2;i<(param.length-1);i+=2) { sourceBuilder.append(DATE_SEPARATOR) .append(getValue(o, param[i])); formatBuilder.append(DATE_SEPARATOR) .append(param[i+1]); } SimpleDateFormat format = new SimpleDateFormat(formatBuilder.toString()); return format.parse(sourceBuilder.toString()); } throw new RuntimeException("Parameters Error"); } private Object getValue(Object o, String name) { try { Field field = o.getClass().getDeclaredField(name); if(!field.isAccessible()) field.setAccessible(true); return field.get(o); } catch (IllegalArgumentException e) { e.printStackTrace(); throw new RuntimeException("Error retrieving field "+name); } catch (SecurityException e) { e.printStackTrace(); throw new RuntimeException("Error retrieving field "+name); } catch (IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException("Error retrieving field "+name); } catch (NoSuchFieldException e) { e.printStackTrace(); throw new RuntimeException("Error retrieving field "+name); } }}
NOTE some small things in converter methods:
A Converter Class must implements Serializable, because it can be serialized in internal JMapper caches
Converter Methods first parameter is Original Object to convert
* Converter Methods can define other 3 parameters that will be auto-wired from JMapper:
* Class: destination class type
* JMapperSession: mapping session in which mapping is done
* String[]: mapping parameters
the user can add after first parameter one or more of these kind of objects in any order and use them to implements mapping.
For Example:
destination class can be used to instantiate destination class in the cases in which mapping does not know which class to instantiate or in which cases user can choose in a range of different destination classes;
mapping session can be used to invoke recursively map(...) method in sub-fields or to retrieve some properties
mapping parameters (as in above examples) to leave user the possibility to pass some properties to mapping, like as, for example, date format in date conversions.
The result of following JUnit test is this one:
@Test public void testPerson_3() { PersonMW_3 personMw = personService.getPerson_3(1); Assert.assertNotNull(personMw); System.out.println("testPerson: personMw="+personMw); try { MapperUtils.Instance.getSession().addConverterClass(DateConverter.class); } catch (JMapperDefinitionException e) { e.printStackTrace(); throw new RuntimeException("Mapping error 0", e); } PersonFE_3 personFe; try { personFe = MapperUtils.Instance.getSession().map(personMw, PersonFE_3.class); } catch (JMapperConversionException e) { e.printStackTrace(); throw new RuntimeException("Mapping error 1", e); } Assert.assertNotNull(personFe); System.out.println("testPerson: personFe="+personFe); Assert.assertEquals(personMw.getGender().getCode(), personFe.getGender()); Object personMw2; try { personMw2 = MapperUtils.Instance.getSession().map(personFe, "Mapping"); } catch (JMapperConversionException e) { e.printStackTrace(); throw new RuntimeException("Mapping error 2", e); } Assert.assertNotNull(personMw2); System.out.println("testPerson: personMw2="+personMw2); Assert.assertNotSame(personMw,personMw2); Assert.assertEquals(personMw,personMw2); Assert.assertTrue(!personMw.getObjectCreationDate().equals(((PersonMW_3)personMw2).getObjectCreationDate())); AddressMW_3 addressMw = personMw.getAddresses().iterator().next(); AddressMW_3 addressMw2 = null; for(AddressMW_3 addressMw2It : ((PersonMW_3)personMw2).getAddresses()) { if(addressMw.getCode()==addressMw2It.getCode()){ Assert.assertNull(addressMw2); addressMw2 = addressMw2It; } } Assert.assertNotNull(addressMw2); Assert.assertNotSame(addressMw,addressMw2); Assert.assertEquals(addressMw,addressMw2); }
suppose that personService implementation is something like following:
public class PersonService { public PersonMW_3 getPerson_3(long code) { return new PersonMW_3( code, "Obama", "Barack", Gender_2.Male, buildDate(4, 8, 1961, 15, 37, 45, 125), buildDate(4, 11, 2008), buildDate(23, 9, 2012), getAddresses(code)); } private Set<AddressMW_3> getAddresses(long code) { Set<AddressMW_3> addresses = new HashSet<AddressMW_3>(); long acode=code+1; addresses.add(new AddressMW_3(acode++, "Milano", "Via Roma", 73)); addresses.add(new AddressMW_3(acode++, "Roma", "Via Armata", 3)); addresses.add(new AddressMW_3(acode++, "Napoli", "Via XVII Settembre", 1)); return addresses; } private Date buildDate(int day, int month, int year, int hour, int minute, int second) { //... build a java.util.Date object }}
the test will write in stdout following rows:
testPerson: personMw=PersonMW_3 [code=1, surname=Obama, name=Barack, gender=Male, birthDate=Fri Aug 04 15:37:45 CET 1961, registrationDate=Mon Mar 31 00:00:00 CET 10, lastLoginDate=Tue Feb 01 00:00:00 CET 29, addresses=[AddressMW_3 [code=3, city=Roma, street=Via Armata, number=3], AddressMW_3 [code=2, city=Milano, street=Via Roma, number=73], AddressMW_3 [code=4, city=Napoli, street=Via XVII Settembre, number=1]], objectCreationDate=Wed Oct 03 16:23:13 CEST 2012]testPerson: personFe=PersonFE_3 [code=1, surname=Obama, name=Barack, gender=1, birthDate=04/08/1961 15:37:45.125, registrationDate=31/03/0010, lastLoginYear=0029, lastLoginMonth=02, lastLoginDay=01, addresses=[AddressFE_3 [code=3, city=Roma, street=Via Armata, number=3], AddressFE_3 [code=2, city=Milano, street=Via Roma, number=73], AddressFE_3 [code=4, city=Napoli, street=Via XVII Settembre, number=1]]]testPerson: personMw2=PersonMW_3 [code=1, surname=Obama, name=Barack, gender=Male, birthDate=Fri Aug 04 15:37:45 CET 1961, registrationDate=Mon Mar 31 00:00:00 CET 10, lastLoginDate=Tue Feb 01 00:00:00 CET 29, addresses=[AddressMW_3 [code=3, city=Roma, street=Via Armata, number=3], AddressMW_3 [code=2, city=Milano, street=Via Roma, number=73], AddressMW_3 [code=4, city=Napoli, street=Via XVII Settembre, number=1]], objectCreationDate=Wed Oct 03 16:23:14 CEST 2012]