Menu

Technical FAQ with examples

Evgeniy Voronyuk

This documentation is actual for java-object-merger-0.8.5.1.

How to start work with library?

You should create merging context at first.

How to create merging context?

It's very easy:

import net.sf.brunneng.jom; 
... 
IMergingContext context = new MergingContext();


Should the classes of object be the same to perform mapping?

No they shouldn't. Mapper automatically detects similar structure of classes and perform mapping where it is possible.

Should my fields have getters / setters to perform mapping?

Yes, to compare properties of bean they should have public getters, and to modify property during mapping - public setter.

How to perform mapping?

  1. Mapping with creation of target object:
PersonDTO personDto = context.map(person, PersonDTO.class);



2. Mapping on existing object:

context.map(person, personDTO);



What if field on destination class named different then on source class?

Use annotation @Mapping (in package net.sf.brunneng.jom.annotations). Put it on field, or getter, or setter of one of property, and specify associated path to corresponding field. For example:

@Mapping("firstName")
private String name1;


Or from code:

context.forBeansOfClass(Person.class).property("name1").mappedTo("firstName");



Note: associated path can refer to fields in childs object of corresponding object, for example: "basicInfo.firstName".

How to exclude field from mapping?

Use annotation @Skip on field, or getter, or setter.
Or from code:

context.forBeansOfClass(Person.class).property("ssn").setSkipped(true);



What if types of corresponding properties are different?

a) java-object-merger supports some default type conversions:
- implicit conversions between java numeric types (where it is allowed in java without type cast)
- conversions between numeric primitive types and their wrappers.
- conversion of any object to string using toString() method.
- conversion between standard java date & calendar classes.
- conversion string to enum, by name of enum constant.
- conversion between enums, by name of constants.

b) You can provide your own type converter.

class MyDateTimeConverter implements TypeConverter { 

   boolean canConvert(Class fromClass, Class toClass) { 
      return (fromClass.equals(java.util.Date.class) && (toClass.equals(DateTime.class); 
   } 

   Object convert(Class targetClass, Object obj, OperationContextInfo contextInfo) { 
      // implementation here 
   } 

And it will used by this context every time when there are need to convert Date to DateTime.

c) You can specify property converter, when conversion is not need everywhere but only for some property.
Using @Converter annotation:

class WeekDayConverter implements PropertyConverter { 
   // implementation here 


class PersonDTO {
...

   @Mapping("birthDate"
   @Converter(WeekDayConverter.class
   private int birthDayOfWeek; 

...
}

Or from code:

context.forBeansOfClass(PersonDTO.class).property("birthDayOfWeek").setConverter(
       new ConverterMetadata(WeekDayConverter.class));


Can I use context.setConverters() method to set my type converters, and whether this overrides default type converters?

Yes you can. It can be more usefull when you configure MergingContext from spring. This rule apply to other settings in merging context and in metadata classes. Default type converters are not touched, but has less priority then your converters.

Is it possible to map several properties into one?

Easy! Use annotation @MapFromMany with @Converter. In @MapFromMany you specify names of fields to be mapped on current field. In @Converter - class which take collection of fields, and make one field from them by some logic. For example:

class PersonDTO { 
... 

   @MapFromMany(srcProperties = {"firstName""secondName"})
   @Converter(NamesJoiner.class); // <- your class with logic of string joining.
   private String fullName;

...
}

or from code:

BeanPropertyMetadata p = context.forBeansOfClass(PersonDTO.class).property("fullName");
p.setConverter(new ConverterMetadata(NamesJoiner.class)); // <- your converter class
p.setMapping(new MappingMetadata(Arrays.asList("firstName""secondName")));

Note: MapFromMany works only in one direction - you can map only from many properties into one, but not vise versa.

Should collection classes to be same to perform mapping?

No they shouldn't. You can map from list to array, or array to set, or in any other combination.

When I map collection to beans to existing collection of beans, how mapper will know what beans from source collection corresponds to beans in destination collection?

Use annotation @Identifier to mark property which is considered to be indentifier of entity. And mapper will compare beans with equal identifiers. If identifier is not specified then beans will be compared by equals method of it.

@Identifier
private Integer identifier;


Or you can specify from code, that for all beans all properties which name is "identifier" are identifiers!

context.forBeansOfClass(Object.class).forMatchedProperties().ofNames(
    "identifier").setIdentifier(true);


This code apply metadata for all beans which extends from Object.class and for all properties with name "identifier". forMatchedProperties() is very powerful mechanism. It allows to set metadata on properties which are lazy matched to specified criteria. It can greatly reduce amount of metadata code.
Note that next code will throw error

context.forBeansOfClass(Object.class).property("identifier").setIdentifier(true); // WRONG!



.. because it will try to find property "identifier" in Object. But if you have some basic type for all entities (for exapmle PersistentEntity) with "identifier" property, then next code will work:

context.forBeansOfClass(PersistentEntity.class).property("identifier").setIdentifier(true);


What if during mapping of collection I need to update just links to some entities (not content)?

You can do it from code with creation of local merging context:

class Author {
   private List<Book> books;
   ...
}

class AuthorDTO {
   private List<BookDTO> books;
   ...
}

IMergingContext localContext = context.localCopy();
localContext.forBeansOfClass(Book.class).mapOnly("identifier");

Author author = localContext.map(authorDto, Author.class);


We create local merging context because don't want to harm original mapping for BookDTO.class. But in local merging context we can do anything, and we mark that for books we want to: mapOnly("identifier").

What if I need some postprocessing of bean, after finishing of mapping?

Use annotation @OnBeanMappingFinished to mark method of bean which will be called after finishing mapping of bean:

class Person {
...

@OnBeanMappingFinished
private onAfterMapping() {
  // your logic here
}

...
}


Or from code:

context.forBeansOfClass(Person.class).method("onAfterMapping").setAfterMapListener(true);


Also you can subscribe on before map starting event using @OnBeanMappingStartring annotation.

Another way is to register global listener:

context.getBeanMappingListeners().add(new IBeanMappingListener() {
   @Override
   public void onEvent(BeanMappingEvent event) {
      // your logic here
   }
});



It will be called for all beans. From event instance you can get bean and type of event.

What if I need to provide some postprocessing logic after finishing mapping of property of the bean?

Use annotation @OnPropertyChange:

class Person {
...
   private String lastName;
...

   @OnPropertyChange(destProperty = "lastName"
                     calledWhen = {PropertyEventType.AFTER_CHANGE}, 
                     listenChanges = {ChangeType.MODIFY})
   private void onLastNameChange(PropertyChangeEvent event) {
      // some logic
   }

...
}

Read javadoc of @OnPropertyChange for details. It is pretty flexible.
Or the same from code:

context.forBeansOfClass(Person.class).method(
     "onLastNameChange").setPropertyListenerMetadata(
           new BeanPropertyListenerMetadata("lastName"
               Arrays.asList(PropertyEventType.AFTER_CHANGE),
               Arrays.asList(ChangeType.MODIFY)));


What if I need to control creation of objects?

You should implement your IObjectCreator and register instance of it in MergingContext.

context.addObjectCreator(new IObjectCreator() {
   @Override
   public Class getCreatedObjectSuperclass() {
      return MyObject.class;
   }

   @Override
   public Object create(Class targetClass, OperationContextInfo contextInfo) throws ObjectCreationException {
      return new MyObject(contextInfo.getDestPropertyDescriptor().getName());
   }
});

In this example name of destination property is passed to constructor of MyObject.

Can mapper, for example, find my beans in database rather than create them?

This can be done by implementing IBeanFinder and registering instance of it in MergingContext.

context.getBeanFinders().add(new IBeanFinder() {
   @Override
   public Class getFoundBeanSuperclass() {
      return MyBean.class;
   }

   @Override
   public Object find(Class targetBeanClass, Object identifier) {   
      // logic of finding ...
      
      return res;
   }
});

Here are 'find' method take class of bean and identifier of bean (from property marked as @Identifier, mentioned above). Using this info it is no problem to find entity it in database.

What if I want to forbid some operations on property, for example DELETE and REPLACE and allow only ADD?

This can be done by annotation @SkipPropertyChanges on destination property, or @SkipPropertyChangesOnDest on source property. This annotation accept list of change types that should be skipped.

@SkipPropertyChanges({ChangeType.DELETE, ChangeType.REPLACE})
private String name1;


Moreover, in same way you can skip container entry changes using annotations @SkipContainerEntryChanges (skip for example deletion entries from collection etc) and @SkipContainerEntryChangesOnDest.

If annotations are not appropriate for you then the same can be done from code:

context.forBeansOfClass(Person.class).property("name1").skipPropertyChanges(
     ChangeType.DELETE, ChangeType.REPLACE);


Note, that operations are skipped only one one side of association. Mapping on another side of association will be work as before. This allows to implement one side mapping for selected properties.

Hibernate throws error when PersistentBag is set to null, can I somehow clean collection on entity instead of setting it to null?

Yes. You can do it globally by setting:

context.getRootConfiguration().setPropertyValue(
    Configuration.CLEAN_COLLECTION_INSTEAD_REMOVE, true);


Or by creating your own configuration with Configuration.CLEAN_COLLECTION_INSTEAD_REMOVE set to true, and use this configuration for your bean.

@ConfigurationUsed("myConfig")
public static class MyBean {
...
}

Then register configuration:

Configuration c = new Configuration("myConfig");
c.setPropertyValue(Configuration.CLEAN_COLLECTION_INSTEAD_REMOVE, true);
context.getRootConfiguration().setSubConfigurations(Arrays.asList(c));


Is there way to control by source bean what properties should be mapped and what should be skipped?

You can need this when source acts as diff of changes, rather then prototype of of what destination should be. There is several ways to achieve this.

1) Every property on source is wrapped by generic wrapper (for example JAXBElement, when you use web services). When wrapper is null then corresponding property should not be wrapped. When it is not null, then wrapped value should be mapped.

Example of implementation:

class Wrapper<T> {
   private String name;
   private T value;

   public Wrapper(String name, T value) {
      this.name = name;
      this.value = value;
   }

   public T getValue() {
      return value;
   }

   public void setValue(T value) {
      this.value = value;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }
}

This is generic wrapper. Lets suppose that name property should contain contain corresponding name of property.

Using this wrapper on source:

class MySourceBean {
   private Wrapper<String> field1;

   public Wrapper<String> getField1() {
      return field1;
   }

   public void setField1(Wrapper<String> field1) {
      this.field1 = field1;
   }
}

On destination:

class MyDestBean {
   private String field1;

   public String getField1() {
      return field1;
   }

   public void setField1(String field1) {
      this.field1 = field1;
   }
}

Now we should somehow convert from wrapper to wrapped value, and vice versa:

context.addTypeConverter(new TypeConverter() {
    @Override
    public boolean canConvert(Class fromClass, Class toClass) {
       return Wrapper.class.isAssignableFrom(toClass) && 
             !Wrapper.class.isAssignableFrom(fromClass);
    }

    @Override
    public Object convert(Class targetClass, Object obj, OperationContextInfo contextInfo) {
       return new Wrapper<Object>(contextInfo.getDestPropertyDescriptor().getName(), obj);
    }
});

context.addTypeConverter(new TypeConverter() {
    @Override
    public boolean canConvert(Class fromClass, Class toClass) {
       return !Wrapper.class.isAssignableFrom(toClass) && 
               Wrapper.class.isAssignableFrom(fromClass);
    }

    @Override
    public Object convert(Class targetClass, Object obj, OperationContextInfo contextInfo) {
       if (obj == null) {
          return EQUAL_TO_DESTINATION_MARKER;
       }

       return ((Wrapper)obj).getValue();
    }
});


Using this converters wrapper can be used not only for primitive values, but also for any other types (beans, collections, maps). EQUAL_TO_DESTINATION_MARKER is returned when wrapper is null to mark that destination object should not be changed.

Also you need to add object creator for mapper:

context.addObjectCreator(new IObjectCreator() {
    @Override
    public Class getCreatedObjectSuperclass() {
       return Wrapper.class;
    }

    @Override
    public Object create(Class targetClass, OperationContextInfo contextInfo) 
           throws ObjectCreationException {

       return new Wrapper(contextInfo.getDestPropertyDescriptor().getName(), null);
    }
});


In both type converter and object creator OperationContextInfo is used to resolve destination property name.

That's all you should do to implement wrappers. And the second approach to control mapping on source bean is:

2) Every property on source bean have corresponding boolean property, which define whether first property should be mapped or not. Lets name this boolean field as specified field.

Using specified field on source:

class MySourceBean { 
   private String field1; 
   private boolean field1Specified;

   public String getField1() { 
      return field1; 
   } 

   public void setField1(String field1) { 
      this.field1 = field1; 
   } 

   public boolean isField1Specified() {
      return field1Specified; 
   }

   public void setField1Specified(String field1Specified) { 
      this.field1Specified= field1Specified; 
   } 
}


Destination object is same as in example with wrapper approach. You can take as convention that all specified field name is property_name + "Specified" suffix. Then the only you should do is to tell mapper about this convention:

context.forBeansOfClass(Object.class).forMatchedProperties().all().setSpecifiedFlagPropertyNameResolver(
              new ISpecifiedFlagPropertyNameResolver() {
    @Override
    public String getSpecifiedFlagPropertyName(PropertyDescriptor targetProperty) {
        return targetProperty.getName() + "Specified";
    }
});


setting context.forBeansOfClass(Object.class).forMatchedProperties().all() means that this will be applied for all types of beans and for all properties. But you can easy set another suffixes for some of your beans if you want - settings are very flexible.

Is there way to control on source collection what changes should be performed with elements of destination collection? For example can I hold on source collections beans which should be removed?

Yes, this question is a little bit similar to previous. You can need this when source collection acts as diff of changes, rather then prototype of of what destination collection should be.

Lets suppose we have next class of source bean with collection property, and collection entry bean which contain type of change.

public class MySourceBean {

    private List<MyCollectionEntryBean> beans;

    public List<MyCollectionEntryBean> getBeans() {
        return beans;
    }

    public void setBeans(List<MyCollectionEntryBean> beans) {
        this.beans = beans;
    }
}

public class MyCollectionEntryBean {

    // some data fields

    private ChangeType changeType;

    public getChangeType() {
        return changeType;
    }

    public setChangeType(ChangeType changeType) {
        this.changeType = changeType;
    }
}


Then tell mapper to use this changeType of MyCollectionEntryBean:

context.forBeansOfClass(MySourceBean.class).property("beans").markContainerActsAsDiff(
  new IContainerEntryChangeTypeAdviser() {
    @Override
    public ChangeType adviceChangeType(Object srcContainerEntry) {
        return ((MyCollectionEntryBean) srcContainerEntry).getChangeType();
    }
});


Now if for example changeType will be ChangeType.DELETE then corresponding destination elements will be deleted etc.


Related

Wiki: Home

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.