Java Object mapping framework working with builder pattern - java

Is there any class mapping framework which works with builders? I would like to keep some of my classes immutable and avoid multiple constructors - the Builder Pattern comes to the rescue. However I can't any mapping framework which would use builder automatically instead of getters/setters.

I got the following working with Lombok and ModelMapper. See: http://modelmapper.org/getting-started/
public class MyService {
private ModelMapper modelMapper;
public MyService(){
this.modelMapper = new ModelMapper();
this.modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setDestinationNamingConvention(LombokBuilderNamingConvention.INSTANCE)
.setDestinationNameTransformer(LombokBuilderNameTransformer.INSTANCE);
}
public OutputDTO aMethod(final InputDTO input){
return modelMapper.map(input, OutputDTO.OutputDTOBuilder.class).build();
}
}
Where LombokBuilderNamingConvention is:
import org.modelmapper.spi.NamingConvention;
import org.modelmapper.spi.PropertyType;
public class LombokBuilderNamingConvention implements NamingConvention {
public static LombokBuilderNamingConvention INSTANCE = new LombokBuilderNamingConvention();
#Override
public boolean applies(String propertyName, PropertyType propertyType) {
return PropertyType.METHOD.equals(propertyType);
}
#Override
public String toString() {
return "Lombok #Builder Naming Convention";
}
}
And LombokBuilderNameTransformer is:
import org.modelmapper.spi.NameTransformer;
import org.modelmapper.spi.NameableType;
public class LombokBuilderNameTransformer implements NameTransformer {
public static final NameTransformer INSTANCE = new LombokBuilderNameTransformer();
#Override
public String transform(final String name, final NameableType nameableType) {
return Strings.decapitalize(name);
}
#Override
public String toString() {
return "Lombok #Builder Mutator";
}
}
And OutputDTO can look like:
#Builder // Has .builder() static method
#Value // Thus immutable
public class OutputDTO {
private String foo;
private int bar;
}

This can be easily done with MapStruct and using a custom naming strategy for builders.
Have a look here in the documentation how to use Custom Accessor naming strategy.
Your mappings then need to look like:
#Mapper
public interface MyMapper {
default Immutable map(Source source) {
return mapToBuilder(source).build();
}
Immutable.Builder mapToBuilder(Source source);
}
Within MapStruct we are already working on a feature that would support out of the box support for builders. You can follow this issue for more details.
Update
MapStruct now (since 1.3.0.Beta1) has out of the box support for Immutables. This means that the mapper before can be written like:
#Mapper
public interface MyMapper {
Immutable map(Source source);
}
The assumption is that there is a public static method without parameters in Immutable that returns the builder

Uing Lombok and ModelMapper configure as:
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PRIVATE);
By default ModelMapper uses only public setter method to map. When the class annotated with Lombok builder annotation it made the setter method as private. So to allow the ModelMapper to use the private setter method we need to add the above configureation.
OR
Configuration builderConfiguration = modelMapper.getConfiguration().copy()
.setDestinationNameTransformer(NameTransformers.builder())
.setDestinationNamingConvention(NamingConventions.builder());
modelMapper.createTypeMap(MyEntity.class, MyDto.MyDtoBuilder.class, builderConfiguration);
where MyEnity class is:
#Data
private static class MyEntity {
private Long id;
private String name;
private String value;
}
and builder class is:
#Data
#Builder
private static class MyDto {
private final Long id;
private final String name;
private final String value;
}
click here for detail

Related

Spring Boot - Apply persistence Converter Annotation based on a condition

I have below repository model class
class Model {
#Column(name="id")
private static Integer id;
#Column(name="column-to-be-converted")
#Convert(converter=Converter.class)
private static String columnToBeConverted;
#Column(name="apply-converter")
private static boolean applyConverter;
// Getters and Setters
}
Below is the Converter class
#Component
#Converter
public class PasswordConverter implements AttributeConverter<String, String> {
#Override
public String convertToDatabaseColumn(String rawData) {
// logic goes here
}
#Override
public String convertToEntityAttribute(String convertedData) {
// logic goes here
}
}
I want to apply #Convert annotation to the field columnToBeConverted only if the field applyConverter is set to true
I tried investigating if the model object can be passed to Converter Class as argument or with using #Conditional
Please suggest how can this be achieved
Thank you!

How to define reusable classes with dynamic types inside?

I have a class SuccessResponse that contains another custom class SuccessCustomerResponseBody, both of these classes are a POJO model for some XML structure.
#JacksonXmlRootElement(localName = "response")
public class SuccessResponse {
private SuccessCustomerResponseBody ok;
public SuccessCustomerResponseBody getOk() {
return ok;
}
public void setOk(SuccessCustomerResponseBody ok) {
this.ok = ok;
}
}
and SuccessCustomerResponseBody
#Getter
#Setter
public class SuccessCustomerResponseBody {
#JacksonXmlElementWrapper(localName = "customers")
private List<Customer> customers;
private String requestId;
#JacksonXmlProperty(localName = "customer")
public List<Customer> getCustomers() {
return customers;
}
}
This model is used to fetch data from a database, and return as an XML response in service controller GetCustomer. It looks okay except when it's required to add another controller to return for example customer packages. So, the easiest way, to create two more classes like SuccessPackageResponse and SuccessPackageResponseBody, rename SuccessResponse to SuccessCustomerResponse and that's it. Well done
#JacksonXmlRootElement(localName = "response")
public class SuccessPackageResponse {
private SuccessPackageResponseBody ok;
public SuccessPackageResponseBody getOk() {
return ok;
}
public void setOk(SuccessPackageResponseBody ok) {
this.ok = ok;
}
}
SuccessPackageResponseBody
#Getter
#Setter
public class SuccessPackageResponseBody {
#JacksonXmlElementWrapper(localName = "packages")
private List<Package> packages;
private String requestId;
#JacksonXmlProperty(localName = "package")
public List<Package> getPackages() {
return packages;
}
}
The main problem is that this approach produces a lot of code. So, I tried to re-use SuccessResponse by implementing this class as generic one.
#JacksonXmlRootElement(localName = "response")
public class SuccessResponse<T> {
private T ok;
public T getOk() {
return ok;
}
public void setOk(T ok) {
this.ok = ok;
}
}
Looks better, right? We can use it like:
SuccessResponse<SuccessCustomerResponseBody> successCustomerResponse = objectMapper.convertValue(node.getMap().get("response"), SuccessResponse.class);
I thought that this is my solution, but I found that ObjectMapper configs are not applied in such approach and I don't understand why.
objectMapper.configOverride(String.class).setSetterInfo(JsonSetter.Value.forValueNulls(Nulls.AS_EMPTY));
To finalise, I have two questions:
Any idea how to re-use SuccessResponse in case when custom type (SuccessCustomerResponseBody) can be dynamically changed.
Why ObjectMapper config isn't applied in solution with generic?
I suppose the reason of losing ObjectMapper config is related to Java Type Erasure, because element type information is not available to Jackson in runtime.
So, to fix it I decided to use TypeReference to set correct type.
objectMapper.convertValue(node.getMap().get("response"), new TypeReference<MyCustomType>() {});
After some research I found another way to resolve my issue with Generic Data type. JavaType is quite useful for my case. Please, see the code below
JavaType customerResponseDataType = objectMapper.getTypeFactory().constructParametricType(SuccessResponse.class, SuccessCustomerResponseBody.class);
SuccessResponse<SuccessCustomerResponseBody> successCustomerResponse = objectMapper.convertValue(node.getMap().get("response"), customerResponseDataType)
where SuccessResponse.classis generic with input type SuccessCustomerResponseBody.class

Changing one dto to another

#Getter
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class GenerateDaByContextDto {
private String cNumber;
private BusinessContext businessContext;
private String zCode;
private String yCode;
private String xCode;
private String event;
public GenerateContentDto toGenerateContentDto() {
return GenerateContentDto.builder()
.businessContext(businessContext)
.event(event)
.build();
}
}
I was making code review, when i wondered is it fine to change DTO's like that?
The need was that some methods have GenerateContentDto as param and it could be acquired from GenerateDaByContextDto DTO in the code.
Is there another option to make it better? Is it good regarding SRP rule?
I have simplified the DTOs fields.
Strongly speaking, it's opinion based and depends on project.
But let's remember single responsibility principle. DTO's responsible for data holding between layers, not for conversion. I prefer to have a simple converter with method like:
public class GenerateDaByContextDtoConverter {
public GenerateContentDto convert(GenerateDaByContextDto source) {...}
}
By the same reason, usually DTOs are immutable. You could use lombok's #Value annotation.
The one more solution may be composition, if it consistent with the business logic :
class GenerateDaByContextDto {
private GenerateContentDto generateContentDto;
...
}
You can replace #Getter ,#Builder,#AllArgsConstructor,#NoArgsConstructor with #Data
this is the better way to do it
#Data
public class GenerateDaByContextDto {
private String cNumber;
private BusinessContext businessContext;
private String zCode;
private String yCode;
private String xCode;
private String event;
/*
public GenerateContentDto toGenerateContentDto() {
return GenerateContentDto.builder()
.businessContext(businessContext)
.event(event)
.build();
}
*/
}

Mapstruct Invoking implicitly other mapper with multiple parameter

Given the following classes and a mapper that takes mulitple source arguments
(I use lombok to keep source as short as possible.)
#Getter
#Setter
public class MySourceOne {
private String src1;
}
#Getter
#Setter
public class MySourceTwo {
private String src2;
}
#Getter
#Setter
public class MyTargetObject {
private String prop1;
private String prop2;
}
#Mapper
public interface MyTargetObjectMapper {
#Mapping(target="prop1", source="a")
#Mapping(target="prop2", source="b")
public MyTargetObject mapMyObject(String a, String b);
}
#Getter
#Setter
public class MyComplexTargetObject {
private MyTargetObject myTargetObject;
}
I am trying to create a mapper for MyComplexTargetObject that will invoke implicitly the MyTargetObjectMapper .
But the "source" won't allow to map multiple parameter like this
#Mapper(uses= {MyTargetObjectMapper.class})
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject", source="one.src1, two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
So I am trying to use an expression="..." instead of source, but nothing works so far.
Any thoughts a clean way to do this without calling the MyTargetObjectMapper in a concrete method?
MapStruct does not support selection of methods with multiple sources.
However: you can do target nesting to do this.
#Mapper
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject.prop1", source="one.src1" )
#Mapping(target="myTargetObject.prop2", source="two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
And let MapStruct take care of generating the mapper. Note: you can still use a MyComplexTargetObjectMapper to do single source to target to achieve this.

How to use modelMapper to convert nested classes

I have a simple class that I want to map to a DTO class using modelMapper.
class Source {
private String name;
private String address;
List<Thing> things;
// getters and setters follows
}
class Thing {
private String thingCode;
private String thingDescription;
// getters and setters
}
and I want to convert these to a sourceDTO that contains a list of ThingDTOs, for example
class sourceDTO {
private String name;
private String address;
List<ThingDTO> things;
// getters and setters.
}
class ThingDTO {
private String thingCode;
private String thingDescription;
// getters and setters
}
If I drop my list of Things and list of ThingsDTO then modelmapper is a delight to use,
modelMapper.map(source, SourceDTO.class);
But I can't work out how to get the mapper to convert the List of Things to List of ThingDTOs. From the documentation, I think I need to create a mapper class that extends PropertyMap but I can't work out how to configure it.
Any pointers to the relevant documentation would be welcome
I think if you configure your ModelMapper as LOOSE or STANDARD it will do for you.
modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE);
Otherwhise you could try next:
You may create a converter like:
public class ListThingToThingDTOConverter implements Converter<List<Thing>, List<ThingDTO>> {
#Override
public List<ThingDTO> convert(MappingContext<List<Thing>, List<ThingDTO>> context) {
List<Thing> source = context.getSource();
List<ThingDTO> output = new ArrayList<>();
...
//Convert programmatically List<Thing> to List<ThingDTO>
...
return output;
}}
Then customize a Mapping Thing to ThingDTO as next:
public class SourceToSourceDTOMap extends PropertyMap<Thing, ThingDTO> {
#Override
protected void configure(){
using(new ListThingToThingDTOConverter()).map(source.getThings()).setThings(null);
}
Finally you must add SourceToSourceDTOMap to your ModelMapper as below:
modelMapper = new ModelMapper();
modelMapper.addMappings(new SourceToSourceDTOMap());
You can map like the below code by creating generics . link for reference
http://modelmapper.org/user-manual/generics/
imports :
import java.lang.reflect.Type;
import org.modelmapper.ModelMapper;
import org.modelmapper.TypeToken;
In your service or controller class:
ModelMapper modelMapper = new ModelMapper();
Type listType = new TypeToken<SourceDTO>(){}.getType();
SourceDTO sourceDTO = modelMapper.map(source,listType);

Categories

Resources