Skip to content

Explicit Conversions

Alessandro Vurro edited this page Oct 12, 2017 · 49 revisions

Explicit conversion allows you to define your own control logic and / or processing data belonging to the source fields and assign them to the destination fields.
You can define conversions through the use of methods and you can do it in java, using the @JMapConversion annotation, in XML using the <conversion> node and in API through the Conversion class.
These give the same possibility and use the same parameters but in different format.

IMPORTANT!

  • The XML and API conversions have greater visibility of the equivalent annotated. For a specific relation is found first in the XML/API conversion method, if not found, will be done a search of the annotated conversion method if exist.
  • The conversion, once satisfied the relationship, replaces the basis mapping of the framework.
  • If there are more conversion methods satisfying the relationship, the first found will be considered.
  • The code must be written according the javassist specifications (if you are using the default bytecode generator).

The position of the conversion is independent from the configuration location
the conversions can be written in the Destination or Source, regardless the configuration location.
different combinations of configuration are permitted
You can also do a combination with all configuration format, for example: configuration with annotations and conversions in xml or vice versa.
you must always and only specify the names of the fields, also in case of nested mapping
For example: if we have a field in Source mapped with ${destination.nestedDestination} the from and to parameters will be respectively: "source" and "nestedDestination".

The conversion between two fields is applied only when there is a relationship between them, follows an example of not applied conversion:

class Destination{                  class Source{
    @JMap
    private String id;                  private String id;
    @JMap("sourceField")
    private String destinationField;    private String sourceField;
    private String other;               private String other;

                                        @JMapConversion(from={"id"}, to={"other"})
                                        public String conversion(String str){
                                            return str;
                                        }
    // getters and setters... 	        // getters and setters...
}                                   }

Assuming to initialize the JMapper class in the following way:

JMapper jmapper = new JMapper<>(Destination.class, Source.class);

relations expressed:

  • Source.id -> Destination.id
  • Source.sourceField -> Destination.destinationField

The conversion instead is defined for the relation:

  • Source.id -> Destination.other

So in this case the conversion will never be applied, the same happens also for the configuration in XML and API format.

Configuration Parameters

After a short overview, we will see what are the configuration parameters common to all types of mapping.
We discuss them at a logical level, for use in detail see the relative section.
The following is a summary table:

Configuration parameter Description Default Value
name (required) name of the method Undefined
from (optional) names of the source fields ALL
to (optional) names of the destination fields ALL
type (optional) conversion type STATIC
avoidSet (optional) avoid set method usage false

The name is essential to distinguish the conversion methods between their.
The parameters from and to indicate which fields will be part of the conversion.
The from indicates the source, the to indicates the recipients, the default value for each is ALL which mean that the conversion will be applied to all related fields.
The Type allows you to define the conversion modes and has only two values: STATIC and DYNAMIC, it will be explained later.
avoidSet avoids the call to the set method for the destination instance if it's set to true.
These are the configuration parameters that can be used in all types of mapping, below we will see how to do for each type.

Annotation

This is an example of configuration in annotation style:

@JMapConversion (from = {"srcField1","srcField2"}, to={"destinationField"}, avoidSet=false, type=Type.STATIC)

The name is not required because is used the conversion method name.

XML

An example in xml format:

<conversion name="conversion" from="srcField1,srcField2" to="destinationField" avoidSet="false" type="STATIC">

API

configuration in API:

conversion("conversionName")
   .from("srcField1","srcField2")
   .to("destinationField")
   .avoidSet(false)
   .type(Type.STATIC)

How conversion works

The generated code is surrounded by a Try-Catch inserted with the aim of managing all the errors generated at runtime, such as NullPointerException.
The conversion methods can be of two types: STATIC and DYNAMIC, static when is used for a specific relationship, dynamic when the method's body change according the relationship that must be satisfied.

The placeholders that can be used in all configuration types are:

Placeholder Description
${source} source reference
${destination} destination reference
${source.type} source type
${destination.type} destination type
${source.name} source name
${destination.name} destination name
${destination.get} destination get method name
${destination.set} destination set method name
${source.get} source get method name
${source.set} source set method name

Static conversion

A conversion is called static when its body does not change with the defined relations.

Suppose we have two classes: Destination and Source, both with 3 fields of type String: d1, d2 and d3 for Destination and s1, s2, and s3 for Source and that you have configured s1 with d1, d2 with s2 and d3 with s3.
We want to define a conversion to be applied to all relationships, then the method will be defined (e.g. in annotation):

@JMapConversion(from={"s1","s2","s3"}, to={"d1","d2","d3"})
public String conversion(String source){
   return source + " converted";
}

There are some important requirements to respect in the implementation of the method (valid only for annotation):

  • the input field to the method must be the same type of the source fields
  • in case of definition of two input, the first from the left must be the same type of destination, the second the same type of source:
@JMapConversion(from={"s1","s2","s3"}, to={"d1","d2","d3"})
public String conversion(String destination, String source){
   return destination + source + " converted";
}
  • the output field to the method must be the same type of destination fields

Assuming that this method is defined in the Destination, the mapping generated at runtime will be as follows:

destination.setD1(destination.conversion(source.getS1()));
destination.setD2(destination.conversion(source.getS2()));
destination.setD3(destination.conversion(source.getS3()));

This conversion is the most common and also the most useful, allows to pool a manipulation that can affect more data.
In case there are any static methods with the same name, will be considered only the first one that meets the requirements.

XML

Consists in the definition of the conversion node, whose value is the body of the method to create.
To refer to the source field and destination field you must use the following placeholders:

  • ${source} to refer to the source field
  • ${destination} to refer to the destination field

Example:

<conversion name="conversion" from="anInteger" to="aString">
   return ${source}.toString();
</conversion>

Method created at runtime:

public String conversion(Integer source){
   return source.toString();
}

Mapping generated:

destination.setAString(conversion(source.getAnInteger()));<br>

If you want to act even on the destination, just use the placeholder: ${destination}.

<conversion name="conversion" from="sourceField" to="destinationField">
   return ${destination}+" "+${source}+" converted";
</conversion>

Method created at runtime:

public String conversion(String destination, Integer source){
   return destination +" "+ source +" converted";
}

Mapping generated:

destination.setAString(conversion(destination.getAString(),source.getAnInteger()));
API

The body definition in this case is equal to XML format:

conversion("conversionName")
   .from("sourceField")
   .to("destinationField")
   .body("return ${source}.toString();");
   

Dynamic conversion

For dynamic conversion we means a method whose body is adapted to every relationship.
The conversion method defined is always one, but at runtime will be created a number of conversions equal to the number of relations.
The dynamic conversion allows to pool the basic logic varying only some values.

There are some important requirements to respect in the implementation of the method (valid only for annotation):

  • The method must be public static
  • no input parameters
  • must return a string containing the dynamic mapping

Example (in annotation):

@JMapConversion(from={"s1,s2,s3"}, to={"d1,d2,d3"}, type=Type.DYNAMIC)
public static String conversion(){
   return "return \"${destination.name} ${source.name}\";";
}

Method created at runtime:

public String fromS1ToD1(){
   return "d1 s1";
}

public String fromS2ToD2(){
   return "d2 s2";
}

public String fromS3ToD3(){
   return "d3 s3";
}

Generated mapping:

destination.setD1(fromS1toD1());
destination.setD2(fromS2toD2());
destination.setD3(fromS3toD3());

As you can see, the methods do not take input data because destination and source aren't referenced with the placeholder ${source} and ${destination}.

Below an example of the use of destination and source references:

@JMapConversion(from={"anInteger"}, to={"aString"}, type=Type.DYNAMIC)
Public static String conversion(){
 return "${destination.type} result = ${source} == 2?\"${source.name}\":\"${destination.name}\";"
     +  "return result + ${destination};";
}

Method created at runtime:

public String FROManIntegerTOaString(String destination, Integer source){
   String result = source == 2? "anInteger":"aString";
   return result + destination;
}

Generated mapping:

dest.setAString(fromAnIntegertoAString(dest.getAString(),source.getAnInteger()));
XML

In XML format:

<conversion name="conversion" from="anInteger" to="aString" type="DYNAMIC">
   ${destination.type} result = ${source} == 2?"${source.name}":"${destination.name}";
   return result + ${destination};
</conversion>
API

API version:

conversion("conversionName")
   .from("anInteger")
   .to("aString")
   .type(Type.DYNAMIC)
   .body("${destination.type} result = ${source} == 2?\"${source.name}\":\"${destination.name}\";"+
         "return result + ${destination};");  
Clone this wiki locally