Using mapstruct v1.0.0.Final, I'm facing an ambiguous mapping exception trying to map from SourceType to TargetType:
class TargetType {
List<TargetTypeChild> children;
boolean allResults;
}
class SourceType {
List<SourceTypeChild> children;
boolean allResults;
}
The mapping that I'm using is:
#Mapper(uses = B.class)
interface A {
#Mapping(target = "children", source = "children", qualifiedBy = ToTargetType.class)
TargetType toTargetType (SourceType source);
#Mapping(target = "children", source = "children", qualifiedBy = ToTargetTypeNoDetails.class)
TargetType toTargetTypeNoDetails (SourceType source);
}
interface B {
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface ToTargetType {}
#Qualifier
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface ToTargetTypeNoDetails {}
#ToTargetType
#IterableMapping(qualifiedBy = ToTargetType.class)
List<TargetTypeChild> withDetails(List<SourceTypeChild> value);
#ToTargetTypeNoDetails
#IterableMapping(qualifiedBy = ToTargetTypeNoDetails.class)
List<TargetTypeChild> noDetails(List<SourceTypeChild> value);
#ToTargetType
#Mappings({
#Mapping(target = "details", source = "details"),
...rest of mapping
})
TargetTypeChild toTargetTypeChild(SourceTypeChild source);
#ToTargetTypeNoDetails
#Mappings({
#Mapping(target = "details", ignore = true),
...rest of mapping
})
TargetTypeChild toTargetTypeChildNoDetails(SourceTypeChild source);
}
This does not compile, giving the following exception in both interface A's methods:
Ambiguous mapping methods found for mapping property "List children" to List: List noDetails(List arg0), List withDetails(List arg0).
There is one workaround to this: put both interface A's methods in interface B. That compiles and works. But I need to separate them for business reasons.
Could anyone explain why the first approach doesn't work and the workaround does?
As a bonus question, if I only code 1 method for mapping (no qualifiers), I don't need to even declare the #IterableMapping method, mapstruct knows how to find the "children" methods.
Why?
Thank you all!
Anyone could explain why the first approach doesn't work and the workaround does?
Your qualifier annotations must at least have retention policy CLASS, only then they will be discovered. That's not needed if everything is defined within the same source file, in which case SOURCE is enough.
As a bonus question, if I only code 1 method for mapping (no qualifiers)
MapStruct will generate (private) iterable mapping methods as needed. Actually it should work also in your original case, seems like that's a glitch we need to fix. I've filed issue #707 for this.
Thanks for reporting this!
Related
I have Pet, Dog and Cat entity classes. Dog and Cat classes extend Pet.
Also I have PetDTO, DogDTO and CatDTO annotated with #JsonSubtype so Jackson resolves well the class of the dtos.
I want to write a mapper using MapStruct that takes a PetDTO entity (can be a DogDTO or a CatDTO) and returns a Dog or a Cat.
For me in this case, the main goal of using a mapping library is to avoid awful code using instanceof.
Any idea? Thanks!
Not currently possible out-of-the-box - see this ticket in mapstruct's GitHub: #366 Support for abstract class mapping or classes with base class. You can try to push it there or maybe contribute this feature yourself. Looks like a reasonable feature to ask for.
I guess that with the current state of affairs this is your best option:
#Mapper
public interface PetMapper {
default PetDTO toPetDto(Pet pet) {
if (pet instanceof Dog) {
return toDogDTO((Dog) pet);
}
if (pet instanceof Cat) {
return toCatDTO((Cat) pet);
}
throw new IllegalArgumentException("Unknown subtype of Pet");
}
default Pet toPetEntity(PetDTO petDTO) {
if (petDTO instanceof DogDTO) {
return toDogEntity((DogDTO) petDTO);
}
if (petDTO instanceof CatDTO) {
return toCatEntity((CatDTO) petDTO);
}
throw new IllegalArgumentException("Unknown subtype of PetDTO");
}
DogDTO toDogDTO(Dog dog);
Dog toDogEntity(DogDTO dogDTO);
CatDTO toCatDTO(Cat cat);
Cat toCatEntity(CatDTO catDTO);
}
The way I ended up implementing a Mapper for a similar case as above was using a combination of a switch-type, with MapStruct Update Existing and creation Mappers.
In my case a property on the source object dictated the subclass we had to generate.
I initially had different mappers for each subtype, but the duplication of the common mapped properties just seemed wrong. So I came up with the following, leveraging the ability of MapStruct to use updating mappers in order to tackle the common parent type properties:
import org.mapstruct.*;
#Mapper
#Named("QualifierPetMapper")
public interface PetMapper {
#Named("DelegatingPetMapper")
#BeanMapping(ignoreByDefault = true)
default PetTarget mapPet(PetSource petSource) {
switch (petSource.getPetType()) {
case "DOG":
DogTarget dogTarget = mapDog(petSource);
updatePet(dogTarget, petSource);
return (dogTarget);
case "CAT":
CatTarget catTarget = mapCat(petSource);
updatePet(catTarget, petSource);
return (catTarget);
default:
throw new CustomException("Unsupported Pet type: "+ petSource.getPetType());
}
}
#BeanMapping(ignoreByDefault = true)
// Specific mappings for Dog
#Mapping(target = "dogfood.name", source = "dogfoodName")
DogTarget mapDog(PetSource petSource);
#BeanMapping(ignoreByDefault = true)
// Specific mappings for Cat
#Mapping(target = "fish.name", source = "favoriteFish")
CatTarget mapCat(PetSource petSource);
#Named("RootPetMapper")
#BeanMapping(ignoreByDefault = true)
// Common properties for Pet
#Mapping(target = "weight.value", source = "weightValue")
#Mapping(target = "name.value", source = "petName")
#Mapping(target = "color", source = "mainColor")
void updatePet(#MappingTarget PetTarget petTarget, PetSource petSource);
}
In MapStruct version 1.1.0.Final, this was possible....
#Mappings({
#Mapping(target = "transaction.process.details", expression = "java(MappingHelper.mapDetails(request))"),
//more mappings
})
Response requestToResponse(Request request);
It was possible, since the mapDetails method was (by coincidence?) generated into the requestToResponse method. That's why request was not null.
Now, since 1.1.0.Final didn't work with Lombok, I had to upgrade to 1.2.0.CR2. With this version, the mapDetails will be generated into a separate method where request is not passed, so request is null within this method now and I get a NPE with the expression. (It's a sub-sub-method of requestToResponse now.)
Did I misuse the expression, so did it just work by coincidence, or does the new version has a bug? If no bug, how do I have to pass the request instance to the expression properly?
You were / are misusing the expression. What you need to do is to map your target to your source parameter.
#Mapper(uses = { MappingHelper.class })
public interface MyMapper {
#Mappings({
#Mapping(target = "transaction.process.details", source = "request"),
//more mappings
})
Response requestToResponse(Request request);
}
MapStruct then should create intermediary methods and use the MappingHelper and invoke the mapDetails method. In case you have multiple methods that map from Request to whatever type details are then you are going to need to used qualified mappings (see more here in the documentation).
It will look something like:
public class MappingHelper {
#Named("mapDetails") // or the better type safe one with the meta annotation #Qualifier
public static String mapDetails(Request request);
}
And your mapping will look like:
#Mapper(uses = { MappingHelper.class })
public interface MyMapper {
#Mappings({
#Mapping(target = "transaction.process.details", source = "request", qualifiedByName = "mapDetails"), //or better with the meta annotation #Qualifier qualifiedBy
//more mappings
})
Response requestToResponse(Request request);
}
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.
I'm a bit stuck and don't understand what's going on.
This one doesn't work
#Entity
#DynamicInsert
#DynamicUpdate
#SelectBeforeUpdate
#Table
class Entity {
#Column(nullable = false)
var owner: String = _
}
val myEntity = new Entity() {
owner = "some owner 1"
}
session.persist(myEntity)
Hibernate throws exception:
java.lang.IllegalArgumentException: Unknown entity:persistence.dao.EntityDaoTest$$anonfun$13$$anonfun$14$$anon$5
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:777)
This one works:
val myEntity = new Entity()
entity.owner = "some owner 1"
session.persist(myEntity)
Why? Why does hibernate don't recognize my Entity instance?
UPD:
#Sheinbergon, thanks, it's clear. I completely forgot that annotations are lost. Is there any possibility to set entity fields with some shortcut?
writing
val myEntity = new MyEntity()
myEntity.owner = "some owner"
myEntity.someOtherProperty = "value"
is super boring
One more question
This one works:
val parent = new Parent
parent.owner = "Our parent"
parent.addChild(new Child() {
name = "First parent's child"
addGrandChild(new GrandChild() {
name = "Grand child name"
addGrandGrandChild(new GrandGrandChild() {
name = "Grand Grand child name"
address = new Address() {
id = 1L
}
})
})
})
Why? Child, GrandChild, GrandGrandChild also created anonymously.
addChild, addGrandChild, addGrandGrandChild are just list mutators.
def addChild(child: Child): Unit = {
if (children == null) {
children = new util.ArrayList[Child]()
}
if (Option(child.parent).isEmpty) {
child.parent = this
}
children.add(child)
}
What you are doing here is instantiating a class anonymously in Scala , and well... that creates an anonymous implementation of your class Entity ( like instantiating an interface anonymously in Java).
you can see it by printing the class name - println(myEntity.getClass) in both cases
Annotations applied to the original class do not apply to the anonymous one (reflection can still find them in the super class, but that's up to the code scanning them) and I guess that's why you're getting the various JPA exceptions
In response to your added sub-questions
Regarding a shortcut - why don't you use companion objects for factories or turn this class into a case class (with defaults), allowing for nicer, more flexible initialization.
Regarding the second object graph(and assuming eachof your classes are annotated) - again it depends on how the reflective code treats the objects it scans. it's possible ( and more likely, given that it won't scan each member of the collection for annotations ) it takes annotation definitions from the erased type ( possible to get it's FQDN class name as ParameterizedType in Java's reflection API) of the collection and not from the actual members of the collection and that's why it works.
I'm not really sure what it does about field definitions though (they are only present in the "super" class), but there's no "magic" here, just plain old reflection scans.
I'm using Spring AOP to intercept a method execution.
I have an interface that looks like the following:
public interface MyAwesomeService {
public Response doThings(int id, #AwesomeAnnotation SomeClass instance);
}
Here is the implementation of the interface:
public class MyAwesomeServiceImpl implements MyAwesomeService {
public Response doThings(int id, SomeClass instance) {
// do something.
}
}
Now i would like any method which has a parameter annotated with #AwesomeAnnotation should be captured by Spring AOP.
So I wrote the following aspect which works.
#Aspect
#Component
public class MyAwesomeAspect {
#Around("myPointcut()")
public Object doAwesomeStuff(final ProceedingJoinPoint proceedingJoinPoint) {
final MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
// annotationMatrix is empty.
}
#Pointcut("execution(public * *(.., #package.AwesomeAnnotation (package.SomeClass), ..))")
public void myPointcut() {}
}
However when I try to find the parameter annotations I don't get any annotations back. As mentioned above, the annotationMatrix is empty.
So here are my questions:
Why is the annotationMatrix empty? Probably because parameter annotations are not inherited from an interface.
Why I'm able to capture the method execution. Since Spring AOP is able match the pointcut, Spring somehow is able to see the method's parameter annotations but when I try to see that using methodSignature.getMethod().getParameterAnnotations() it doesn't work.
I also faced this issue with one of my parameter annotations. I was able to fix the same by making sure that the parameter annotation definition had RetentionPolicy as RUNTIME and Target as PARAMETER
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface Param {
public String name();
}
The answers to your questions:
Parameter annotations are not inherited from interfaces to implementing methods. In fact, annotations are almost never inherited, only from class (not interface!) to subclass if the annotation type itself is annotated by #Inherited, see JDK API documentation.
Update: Because I have answered this question several times before, I have just documented the problem and also a workaround in Emulate annotation inheritance for interfaces and methods with AspectJ.
Because during compile or weave time AspectJ can match your pointcut against the interface method and thus sees the annotation.
You can fix the situation by adding the annotation to the parameter in your interface implementation, e.g. like this:
#Override
public Response doThings(int id, #AwesomeAnnotation SomeClass instance) {
// ...
}
Then with an aspect like this...
#Aspect
#Component
public class MyAwesomeAspect {
#Pointcut("execution(public * *..MyAwesomeService.*(*, #*..AwesomeAnnotation (*), ..)) && args(*, instance, ..)")
static void myPointcut(SomeClass instance) {}
#Around("myPointcut(instance)")
public Object doAwesomeStuff(Object instance, ProceedingJoinPoint proceedingJoinPoint) {
System.out.println(proceedingJoinPoint);
System.out.println(" instance = " + instance);
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
for (Annotation[] annotations : annotationMatrix) {
for (Annotation annotation : annotations) {
System.out.println(" annotation = " + annotation);
}
}
return proceedingJoinPoint.proceed();
}
}
... you get a console log similar to this:
execution(Response de.scrum_master.app.MyAwesomeServiceImpl.doThings(int, SomeClass))
instance = de.scrum_master.app.SomeClass#23fc625e
annotation = #de.scrum_master.app.AwesomeAnnotation()