mapstruct null check causes spotbug error - mapstruct

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.

Related

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);

Map Target object with no source object using 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);
}

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.

Parse HL7 v2.3 REF message with local customizations in HAPI

I am trying parse a HL7 REF I12 message with local customization(NZ).
When I tried using the GenericParser, I keep getting Validation exceptions.
For example for the segment below, I keep get the output
ca.uhn.hl7v2.validation.ValidationException: Validation failed:
Primitive value '(08)569-7555' requires to be empty or a US phone
number
PRD|PP|See T Tan^""^""^^""|""^^^^""^New Zealand||(08)569-7555||14134^NZMC
My question is:
Is there a way to avoid the validation by using the conformance class
generator
Is it possible to create own validation classes using
CustomModelClasses?
In either case, is there any example code for that or tutorial example documentation?
If disabling validation altogether is an option for your application, then you can set the validation context to use NoValidation.
See this thread in the hapi developers mailing list: http://sourceforge.net/p/hl7api/mailman/message/31244500/
Here is an example of how to disable validation:
HapiContext context = new DefaultHapiContext();
context.setValidationContext(new NoValidation());
GenericParser parser = context.getGenericParser();
String message = ...
try {
parser.parse(message);
} catch (Exception e) {
e.printStackTrace();
}
If you still require validation, but just want to change the validator for specific rules, then you'll have to create your own implementation of ValidationContext. This would be done by sub classing ca.uhn.hl7v2.validation.builder.support.NoValidationBuilder and overriding the configure method and use this to instantiate an instance of ValidationContextImpl.
For an example of how to implement the configure method in your subclass of NoValidationBuilder, see the source code for ca.uhn.hl7v2.validation.builder.support.DefaultValidationBuilder. This is the default validation context that is generating the error message you're seeing. To make it easier for you, I'm including the class listing here:
public class DefaultValidationBuilder extends DefaultValidationWithoutTNBuilder {
#Override
protected void configure() {
super.configure();
forAllVersions()
.primitive("TN")
.refersToSection("Version 2.4 Section 2.9.45")
.is(emptyOr(usPhoneNumber()));
}
}
Notice this is the implementation of the usPhoneNumber method defined in BuilderSupport:
public Predicate usPhoneNumber() {
return matches("(\\d{1,2} )?(\\(\\d{3}\\))?\\d{3}-\\d{4}(X\\d{1,5})?(B\\d{1,5})?(C.*)?",
"a US phone number");
}