How to specify default mapping method for Mapstruct - java

I have simple object Client
public class Client {
String externalCode;
String name;
String surname;
}
And I want to map it to nearly identical object
public class User {
String internalCode;
String name;
String surname;
}
See, I want externalCode to be mapped to internalCode. And I have a method which does that. I have marker my method with my custom #CodeMapping annotation and put that annotation to qualifiedBy parameter. So, here is my mapper.
#Mapper()
ClientMapper {
#CodeMapping
String toInternalCode(String externalCode) {
return externalCode + " internal part";
}
#Mapping(target = "internalCode", source = "externalCode", qualifiedBy = CodeMapping.class)
User toUser(Client client);
}
The problem is that name and surname fields are also mapped using toInternalCode method. Mapstruct sees that I have defined a method, which maps String to String and thinks it should be used in all cases.
Is there a way to tell Mapstruct, that if no qualifiers are specified, direct mapping should be used? Or make my own method which takes string and return it and tell Mapstruct it should use that method by default?

Most likely the toInternalCode is used by all methods because the #CodeMapping annotation is not meta annotated with #Qualifier (from org.mapstruct.Qualifier).
The #CodeMapping should be defined in the following way:
import org.mapstruct.Qualifier;
#Qualifier
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.CLASS)
public #interface CodeMapping {
}

Related

Mapstruct: How to merge two fields into one

Using the MapStruct framework, how do you map multiple fields into a single one (based on custom logic) while still mapping the other fields one to one?
Here is a simple example to illustrate what I mean:
public class Source {
private String firstname;
private String surname;
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
public class Target {
private String fullname; // Sould be firstname + surname
// other fields eg:
private String address;
private int age;
private int favoriteNumber;
}
I know it's possible using expressions:
#Mapping(target = "fullname", expression = "java(el.getFirstname() + el.getSurname())")
But in my special use case, not depicted in this example, I need to use some external library for the merging/mapping of the two fields, which isn't feasible with expressions.
Is there a way to achieve merging two fields without expressions?
One approach would be to add a custom mapping method from the Source object to the merged value, and then declare the source for the merged field to be the whole source object:
interface CustomMappingMethodMapper {
#Mapping(target = "fullname", source = ".")
Target map(Source source);
default String getFullName(Source s) {
return s.getFirstname() + " " + s.getSurname();
}
}
You can use #AfterMapping annotation
https://mapstruct.org/documentation/stable/reference/html/#customizing-mappings-with-before-and-after
You would like to replace your interface with abstract class and then
#AfterMapping
void customMapping(#MappingTarget Target target, Source source) {
// any custom logic
}
You claim that calling an external library in an expression isn't feasible. This may not be true, depending on the nature of the library you're calling and the frameworks being used.
Static method
If the method being called is a static method on a class, it can be called directly within the expression annotation element of #Mapping. To avoid having to fully qualify the class being called, the imports element of #Mapper can be used.
#Mapper(imports = ExternalLibrary.class)
public interface SourceToTargetMapper {
#Mapping(target = "fullname",
expression = "java(ExternalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
Target map(Source s);
}
Spring Framework bean
If the library method is a method on a Spring bean, the mapper can be made into a bean using the Spring component model, and the Spring bean containing the library method can be injected into the mapper:
#Mapper(componentModel = "spring")
public static abstract class SourceToTargetMapper {
#Autowired
ExternalLibrary externalLibrary;
#Mapping(target = "fullname",
expression = "java(externalLibrary.toFullName(s.getFirstname(), s.getSurname()))")
abstract Target map(Source s);
}
To use this mapper, inject it as a Spring bean, and call the mapping method on the bean:
#Component
public class Example {
#Autowired
private SourceToTargetMapper mapper;
public void demonstrate(Source s) {
System.out.println(mapper.map(s));
}
}
I haven't tested it myself, but I imagine this injection approach would work with the other component models supported by MapStruct (cdi & jsr330).
I needed to compose 2 basic fields into am Wrapped Object. Here's how I did id
#Mapping(source = "quantity", target = "energyQuantity.quantity")
#Mapping(source = "quantityUnit", target = "energyQuantity.energyQuantityUnit", qualifiedByName = "tradeSearchEnergyQuantityUnitToEnergyQuantityUnit")
Trade tradeSearchToDomainDTO(api.client.generated.model.Trade trade);
where energyQuantity is a Wrapper class for the 2 fields

Ambiguous mapping methods found for mapping property

I have two domain entities:
class Identity {
Long id;
Set<Business> businesses;
}
class Business {
Long id;
String name;
}
I then have two DTOs that extend a base DTO:
class BaseDto {
String id;
}
class IdentityDto extends BaseDto {
Set<BaseDto> businesses;
}
class BusinessDto extends BaseDto {
String name;
}
Then I created a mapper that maps a list of my domain entities to either a Set of the specific dto, or a set of the more generic base dto. This is because when I am getting a list of businesses, I want the full business dto, but when I get an identity, I just what the base info in it's list of businesses.
But when I try to create the mapper for the identity I get the following error:
Ambiguous mapping methods found for mapping property
"Set<Business> businesses" to Set<BaseDto>:
Set<BusinessDto> BusinessMapper.toSet(Set<Business> businesses),
Set<BaseDto> BusinessMapper.toBaseSet(Set<Business> businesses).
I thought that mapstruct used the most specific method, so should know to use the toIdentifierSet method in this case.
How do I make mapstruct know which method to use?
There is no most specific method here as you are trying to map into Set<BaseDto>.
You can use Mapping method selection based on qualifiers.
You can define some annotations:
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface BaseInfo {
}
Then in your BusinessMapper
#Mapper
public interface BusinessMapper {
Set<BusinessDto> toSet(Set<Business> businesses);
#BaseInfo
Set<BaseDto> toBaseSet(Set<Business> businesses);
}
Then in your identifier
#Mapper
public interface IdentifierMapper {
#Mapping(target = "businesses", qualifiedBy = BaseInfo.class)
IdentityDto map(Identity source);
}
In case you want to explicitly pick always you can add another annotation BusinessInfo and then annotate the other method. Then you would need to pick a method each time.

Modify parameter value of custom annotation

Is there any way to implement annotation in order to change his parameter value by itself?
For example:
I would like create custom RequestMapping annotation to get rid of some code duplicates.
Current code:
#RequestMapping("/this/is/duplicate/few/times/some")
public class SomeController {
}
And I want to create something like this
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping()
public #interface CustomRequestMapping {
String value() default "";
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String "/this/is/duplicate/few/times"+value();
}
In order to reduce Request Mapping value to this:
#CustomRequestMapping("/some")
public class SomeController {
}
Unfortunately I cant find way to make that compilable.
Or maybe there is a way to use AliasFor annotation to pass parameter into destination array. Something like this:
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping()
public #interface CustomRequestMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value{1}")
String value() default "";
#AliasFor(annotation = RequestMapping.class, attribute = "value{0}")
String prefixPath() default "/this/is/duplicate/few/times";
}
What it seems is you are trying to make a subtype of an annotation and modify one of its attributes default value
Subtyping of annotation is not possible and here is the JSR stating the reason.
It complicates the annotation type system,and makes it much more difficult
to write “Specific Tools”.
“Specific Tools” — Programs that query known annotation types of arbitrary
external programs. Stub generators, for example, fall into this category.
These programs will read annotated classes without loading them into the
virtual machine, but will load annotation interfaces.
One solution to your duplication problem could be to extract a constant.
#Annotation(MyClass.FOO+"localValue")
public class MyClass
{
public static final String FOO = "foo";
...
}

Java annotation: elements declared as method but value set as attribute

When we create a custom annotation, we declare elements as methods and later set values as if they were attributes.
For example, here we have declared a custom annotation ComponentType with elements name() and description() that look like methods.
public #interface ComponentType {
String name();// declared as method
String description();
}
When the annotation is used, they look like the below:
#ComponentType(name = "userContainer", // value looks like an attribute
description = "a user container")
public class UserEntity { }
My question is: Why doesn't Java allow to declaring elements as attributes, like this?
public #interface ComponentType {
String name; // Compilation Error
String description;
}
If the properties of an annotation weren't defined as abstract methods in an interface, they would have been members. Something like:
public #interface ComponentType {
String name;
String description;
}
However, all the members in an interface are implicitly final (and static) and the above code does not compile, because name and description aren't initialized.
But if they were actually initialized with some values:
public #interface ComponentType {
String name = "name";
String description = "description";
}
then snippets like the following one wouldn't have been possible:
#ComponentType(
name = "userContainer" //cannot assign a value to a final variable
, description = "a user container")
My observation is:
Java consider annotations as special type of interface so:
Like interface we can declare only final attributes in an annotation:
String appName = "test application";//final attribute, never reset value
Annotation may contains only abstract methods(a method that is declared without an implementation).
public #interface ComponentType {
String name();// declared as abstract method
When we annotated elements(e.g. class, method, attribute) by annotation we need to set return value of those abstract methods, which looks like attribute but actually acts as an implementation.
#ComponentType(name = "userContainer"//set return value of method name()
We can use the values we set during annotated elements(e.g. class, method, attribute) by simply calling abstract methods of annotation.
Annotation annotation = annotatedClassObject.getAnnotation(ComponentType.class);
ComponentType componentType = (ComponentType) annotation;
String someName = componentType.name(); //returns value set during annotating
So like as interface,
Annotation never support to declare any non-final attributes.
Annotation may contains some abstract methods and we need to set return value of
abstract method during annotated elements(e.g. class, method,
attribute).
Expecting More Feedback / Answer

Java Reflection: Invoking Setter and Getter method for collection type Object

I have two different packages of User define Objects.....
1) ws.lender.dto (all Objects exists in this package are source side).
2) copl.com.dto (all Objects exists in this package are destination side).
Objects hierarchy and Objects name different in both side. I wan to
copy source side object to destination side object field by field or
via getter and setter using Reflection.
For Example
Source side Objects
package ws.lender.dto;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "CustomerAddresses", propOrder = {
"previousAddresses"
})
public class CustomerAddresses {
protected PreviousAddresses previousAddresses;
public PreviousAddresses getPreviousAddresses() {
return previousAddresses;
}
public void setPreviousAddresses(PreviousAddresses value) {
this.previousAddresses = value;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "PreviousAddresses", propOrder = {
"previousAddress"
})
public class PreviousAddresses {
#XmlElement(name = "PreviousAddress", required = true)
protected List<PreviousAddress> previousAddress;
public List<PreviousAddress> getPreviousAddress() {
if (previousAddress == null) {
previousAddress = new ArrayList<PreviousAddress>();
}
return this.previousAddress;
}
}
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "PreviousAddress", propOrder = {
"streetNo",
"streetName"
})
public class PreviousAddress {
#XmlElement(name = "StreetNo", required = true)
protected String streetNo;
#XmlElement(name = "StreetName", required = true)
protected String streetName;
public String getStreetNo() {
return streetNo;
}
public void setStreetNo(String value) {
this.streetNo = value;
}
public String getStreetName() {
return streetName;
}
public void setStreetName(String value) {
this.streetName = value;
}
}
Destination side Objects
package copl.com.dto;
#javax.persistence.Entity
public class Customer implements java.io.Serializable
{
private Set<CustomerAddress> customerAddresses;
public Set<CustomerAddress> getCustomerAddresses()
{
return customerAddresses;
}
public void setCustomerAddresses(Set<CustomerAddress> customerAddresses)
{
this.customerAddresses = customerAddresses;
}
}
#javax.persistence.Entity
public class CustomerAddress implements java.io.Serializable
{
private String unitNumber;
private String streetName;
private String streetNumber;
public String getUnitNumber()
{
return unitNumber;
}
public void setUnitNumber(String unitNumber)
{
this.unitNumber = unitNumber;
}
public String getStreetName()
{
return streetName;
}
public String getStreetNumber()
{
return streetNumber;
}
public void setStreetName(String streetName)
{
this.streetName = streetName;
}
public void setStreetNumber(String streetNumber)
{
this.streetNumber = streetNumber;
}
}
I think you could use MapStruct to mapping between POJO's that has different attribute names.
But your scenario is complex, because you want to convert ws.lender.dto.CustomerAddresses to copl.com.dto.Customer, and this implies to convert a List<ws.lender.dto.PreviousAddress> contained into a ws.lender.dto.PreviousAddresses object, to a Set<copl.com.dto.CustomerAddress> contained into a copl.com.dto.Customer object.
So, I will explain step by step.
1. Convert from ws.lender.dto.PreviousAddress to copl.com.dto.CustomerAddress
To do this conversion you need an interface (MapStruct will create an instance for this) responsible for mapping from source object to destination object:
import ws.lender.dto.PreviousAddress;
import copl.com.dto.CustomerAddress;
#Mapper
public interface CustomerAddressesMapper{
CustomerAddressesMapper INSTANCE = Mappers.getMapper( CustomerAddressesMapper.class );
#Mappings(#Mapping(source = "streetNo", target = "streetNumber")
CustomerAddress previousToCustomerObject(PreviousAddress address);
}
This interface will map a PreviousAddress object to a CustomerAddress considering that streetNo attribute has to be mapped to streetNumber. There is no mapping for unitNumber attribute cause there is no source for it.
2. Convert a List<ws.lender.dto.PreviousAddress> to Set<copl.com.dto.CustomerAddress>
Now you have to add another mapping method to existing CustomerAddressesMapper interface:
Set<CustomerAddress> previousToCustomerSet(List<PreviousAddress> addresses);
This method will use the former previousToCustomerObject to convert every element of source list to destination set.
3. Convert from ws.lender.dto.CustomerAddresses to copl.com.dto.Customer
Finally, you need to add the last mapping method to CustomerAddressesMapper interface:
#Mappings(#Mapping(source = "previousAddresses.previousAddress", target = "customerAddresses")
Customer customerAddrsToCustomerObject(CustomerAddresses addresses);
This is the where you map an origin object, converting previousAddresses.previousAddress attribute to customerAddresses attribute, using former methods.
4. Using the mapper
To use the mapper you have to write some code as following:
CustomerAddressesMapper mapper = CustomerAddressesMapper.INSTANCE;
CustomerAddresses origin = //Retrieve it from anywhere
Customer dest = mapper.customerAddrsToCustomerObject(origin);
5. Setup
MapStruct is a source code generator, so you need to properly configure your pom.xml to include MapStruct dependency and to invoke this code generation. You can see how to do this here
Well I don't build and run this code, but this is the way to do it.
Hope it helps!
I have studied many objects mapping frameworks for this assignments like
Orika
MapStruct
ModelMapper
Dozer
Commons-BeanUtils
Finally I choose the Orika framework to complete that above Objects to Objects mapping. We can do the that mapping via other mapper framework, but I was like Orika framework because this framework very easy to use for mapping Objects to Objects.
I will explain step by step.
1. Create the Objects of Source side Object and Destination side Object.
like this..
Customer destination = new Customer();
CustomerAddresses source = new CustomerAddresses();
source = filledCustomerAddressesObject();
2. Constructing the DefaultMapperFactory
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
3. Mapping fields
ClassMapBuilder<CustomerAddresses, Customer> builder;
builder= mapperFactory.classMap(CustomerAddresses.class, Customer.class).constructorA();
builder.field("previousAddresses.previousAddress{streetNo}","customerAddresses{streetNumber}");
builder.field("previousAddresses.previousAddress{streetName}","customerAddresses{streetName}");
builder.register();
BoundMapperFacade<CustomerAddresses, Customer> boundMapper;
boundMapper = mapperFactory.getMapperFacade(CustomerAddresses.class, Customer.class);
destination = boundMapper.map(source, destination);
Its work fine Cheers
You can try Object Mapper better for casting or copying. The object to other classes in other packages you can add some property value like
senderClass and rvcClass
Later you can read those properties and proceed for converting the class. Probably you already have mapping ready for sender class against receiver class.
If I understand you correctly, you need a way to copy all like-named properties from one object to another. Like-named properties would be cases in which the source object has a method called something like getPropertyName() and the destination object has one called setPropertyName().
If this is right, then you want to use the copyProperties method of the BeanUtils class from the Apache Commons library. Documentation is here.
Now, in your example, you have some corresponding properties that are not like-named, such as StreetNumber and StreetNo. I'm afraid there's no easy way to handle that sort of thing automatically through reflection; you would need to define the mappings between source and target properties yourself, perhaps by defining a helper class to do the copying.

Categories

Resources