Map Target object with no source object using MapStruct - mapstruct

I want to map a Target object with no source object using MapStruct. I tried it, but getting the below error.
Can't generate mapping method with no input arguments
Mapper code
public interface MyMapper {
#Mapping(target="student.courseName", constant="Master in Science")
Target map();
}

As you can see this is not supported. And why would you do that? Why not just write your own method?
That aside, you can theoretically try passing a dummy parameter that won't be mapped.
public interface MyMapper {
#Mapping(target="student.courseName", constant="Master in Science")
Target map(Integer dummy);
}

Related

mapstruct null check causes spotbug error

We have a mapstruct mapper defined for a source and a target that also use lombok.
#Value
class Source {
#NonNull List<ElementSource> elements;
}
#Value
class Target {
#NonNull List<ElementTarget> elements;
}
#Mapper
public interface Mapper {
Target mapToTarget(Source source);
}
mapstruct generated mapper implementation looks like below code.
List<ElementTarget> elements = null;
List<Element> list = source.getColumns();
if ( list != null ) {
elements = new ArrayList<ElementTarget>( list );
}
Target target = new Target( elements );
return target;
Now this piece of mapper code raises spotbug error with type NP_NULL_PARAM_DEREF: Method call passes null for non-null parameter. Because it detects elements can be null when the code creates Target object.
Now what is the best option to handle this.
Suppress spotbug to ignore this error type for mapstruct generated mapper classes. But the mapper class still has a code smell here.
Define a customzed mapper method for elements itself and throw exception if source object return null list.
if there is a way to configure mapstruct to not generate null check for elements at all.
if there is a way to configure mapstruct to throw exception when null check of elements return true.
For option 3 & 4, I can't find an answer whether mapstruct support it or not.
The ideal solution would be option 3. This is currently not supported by MapStruct. However, it is a requested feature in mapstruct/mapstruct#1243. I would suggest voting for it if you are interested in it.
Option 4 would also be a new feature, that I am not sure how much it belongs in MapStruct, but you can try raising it as a feature request to get the feedback from the community.

Mapping Hierarchy of Classes with Mapstruct

I have a hierarchy of classes: VehicleDTO is a base abstract class.
CarDTO, TruckDTO, VanDTO extend from it.
I have the same hierarchy on the other side of a mapper:
VehicleBO <- CarBO, TruckBO, VanBO.
I want to have all the mapping logic consolidated in one mapper. Period.
I have defined mappings for common attributes, but here is when it becomes interesting, I get this exception during compilation:
The return type ... is an abstract class or interface.
Provide a non abstract / non interface result type or a factory method.
So, how do I specify a factory method, that based on a value of a particular attribute or a class of the pojo, would create a target object for me? I would appreciate a good code snippet that actually does the trick.
Thanks!
You can use a method annotated with #ObjectFactory receiving a source parameter for what you need.
Let's assume that you have a mapper that looks like:
#Mapper
public interface VehicleMapper {
VehicleDTO map(VehicleBO vehicle);
// more
}
If you add a method looking like:
#ObjectFactory
default VehicleDTO createVehicleDto(VehicleBO vehicle) {
// your creation logic
}
Then MapStruct will use the createVehicleDto to create the VehicleDTO object.
NOTE when mapping hierarchies and when the mapping looks like the one in the answer then MapStruct will only map the properties which are in the VehicleDTO class and not in possible implementations of the class. The reason for that is that MapStruct generates the mapping code during compilation and not during runtime.
For mapping hierarchies like what you explained you can do something like the following:
public interface VehicleMapper {
default VehicleDTO map(VehicleBO vehicle) {
if (vehicle instanceOf CarBO) {
return map((CarBO) vehicle);
} else if (vehicle instanceOf TruckBO) {
return map((TruckBO) vehicle);
} else if (vehicle instanceOf VanBO) {
return map((VanBO) vehicle);
} else {
//TODO decide what you want to do
}
}
#Named("car")
CarDTO map(CarBO car);
#Named("truck")
TruckDTO map(TruckBO truck);
#Named("car")
VanDTO map(VanBO van);
// more
}
There is mapstruct/mapstruct#131 requesting for generating code like my example out of the box
Nowadays, maybe using Visitor pattern could be better choice instead of the instanceOf way, check below:
https://techlab.bol.com/en/blog/mapstruct-object-hierarchies
You need to set the subclassExhaustiveStrategy property in your #Mapper annotation to RUNTIME_EXCEPTION.
See Mapstruct documentation:
...
To allow mappings for abstract classes or interfaces you need to set the subclassExhaustiveStrategy to RUNTIME_EXCEPTION, you can do this at the #MapperConfig, #Mapper or #BeanMapping annotations. If you then pass a GrapeDto an IllegalArgumentException will be thrown because it is unknown how to map a GrapeDto. Adding the missing (#SubclassMapping) for it will fix that.
...

MapStruct - How to set different null strategy for different mapping methods?

I want to have a single Mapper class with both create and update methods. The generated code for create method is fine, but in case of update, I want to set the properties in the target, only if they are not null in the source.
How do I do it with mapStruct?
The confusion arises because the nullValueMappingStrategy is being defined at Mapper or Mapping level.
If I set that value at Mapper level, it will be applied to all methods, including create and update.
#Mapper // If I define null strategy here, it will be applied to all methods
public interface AmcPkgMapper {
AmcPkgMapper MAPPER = Mappers.getMapper(AmcPkgMapper.class);
AmcPackage create(AmcPackageRequest amcPackageRequest);
// How to define the null strategy here??
void update(AmcPackageRequest amcPackageRequest, #MappingTarget AmcPackage amcPackage);
}
And if I set it on the method with Mapping, then it expects me to define a target object, for which I probably need a wrapper object and somehow map all the internal properties inside that.
#Mapping(target = "amcPackage", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void update(AmcPackageRequest amcPackageRequest, #MappingTarget AmcPackageWrapper amcPackageWrapper);
With the above method, the generated code looks as below, which isn't going inside amcPackage to set all properties.
#Override
public void update(AmcPackageRequest amcPackageRequest, AmcPackageWrapper amcPackageWrapper) {
if ( amcPackageRequest == null ) {
return;
}
// nothing is mapped actually!!
}
Is there a simple way to do it without creating separate mapper classes for create and update?
Got it done with #BeanMapping
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
void update(AmcPackageRequest amcPackageRequest, #MappingTarget AmcPackage amcPackage);

Extend ProposalProvider in external Eclipse Project via Extension Point

I try to extend my MyDSLProposalProvider from an external Eclipse RCP Project. I created an extension point schema which requires a class property which extends my ProposalProvider. In the new project I extend the class an overrode some methods justs to give me some output so I can see that the external method is called. But this is currently not happening. Is there anything I have to consider?
Currently the hirachy looks like:
MyDSLProposalProvider extends AbstractMyDSLProposalProvider
ExternalProposalProvider extends MyDSLProposalProvider
I rewrote a Method generated in the AbstractMyDSLProposalProvider but when its triggered the predefined Method in the AbstractMyDSLProposalProvider is called and not my new implementation.
public class ExternalMyDSLProposalPovider extends MyDSLProposalProvider
{
#Override
public void completeComponent_Name(EObject model, Assignment
assignment, ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
System.err.println("extern");
if(model instanceof Component)
{
createProposal("foo", "foo", context, acceptor);
}
super.completeComponent_Name(model, assignment, context, acceptor);
}
}
This is the class in the external Eclipse Project.
Thanks for the help.
When you declare an extension point using a schema that you have defined Eclipse puts that declaration in the extension point registry. That is all that is does, you must then write code to make uses of those declarations.
You read the extension point registry using something like:
IExtensionRegistry extRegistry = Platform.getExtensionRegistry();
IExtensionPoint extPoint = extRegistry.getExtensionPoint("your extension point id");
IConfigurationElement [] elements = extPoint.getConfigurationElements();
elements is now an array of the declarations in the various plugins using the extension point.
IConfigurationElement has various methods to get the values of the attributes of the declaration.
If you have defined a class in one of the attributes you can create an instance of the class using:
IConfigurationElement element = .... a config element
Object obj = element.createExecutableExtension("attribute name");
In your case the result should be your ExternalMyDSLProposalPovider.
You will then need to hook this object up with whatever is doing to proposals.

mapStruct: map list to other list?

I have a list List<Payment> which I'd like to map to another list List<PaymentPlan>. These types look like this:
public class Payment {
#XmlElement(name = "Installment")
#JsonProperty("Installment")
private List<Installment> installments = new ArrayList<>();
#XmlElement(name = "OriginalAmount")
#JsonProperty("OriginalAmount")
private BigDecimal originalAmount;
//getters setters, more attributes
}
and....
public class PaymentPlan {
//(Installment in different package)
private List<Installment> installments;
#XmlElement(name = "OriginalAmount")
#JsonProperty("OriginalAmount")
private BigDecimal originalAmount;
//getters setters, more attributes
}
I expect that something like this is working...
#Mappings({
#Mapping(//other mappings...),
#Mapping(source = "payments", target = "paymentInformation.paymentPlans")
})
ResultResponse originalResponseToResultResponse(OrigResponse originalResponse);
...but I get:
Can't map property java.util.List<Payment> to java.util.List<PaymentPlan>.
Consider to declare/implement a mapping method java.util.List<PaymentPlan> map(java.util.List<Payment> value);
I don't know how to apply this information. First I though I need to declare some extra mapping (in the same mapper class) for the lists, so MapStruct knows how to map each field of the List types like this:
#Mappings({
#Mapping(source = "payment.originalAmount", target = "paymentInformation.paymentPlan.originalAmount")
})
List<PaymentPlan> paymentToPaymentPlan(List<Payment> payment);
...but I get error messages like
The type of parameter "payment" has no property named "originalAmount".
Obviously I do something completely wrong, since it sound like it does not even recognize the types of the List.
How can I basically map from one List to another similar List? Obviously I somehow need to combine different mapping strategies.
btw: I know how to do it with expression mapping, like...
#Mapping(target = "paymentPlans",expression="java(Helper.mapManually(payments))")
but I guess MapStruct can handle this by iself.
I presume you are using version 1.1.0.Final. Your extra mapping is correct, the only difference is that you need to define a mapping without the lists MapStruct will then use that to do the mapping (the example message is a bit misleading for collections).
PaymentPlan paymentToPaymentPlan(Payment payment);
You don't even need the #Mappings as they would be automatically mapped. You might also need to define methods for the Instalment (as they are in different packages).
If you switch to 1.2.0.CR2 then MapStruct can automatically generate the methods for you.