mapStruct: map list to other list? - mapstruct

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.

Related

AssertJ: How to build custom and nested assertions

I need to compare one PatientDTO dto object with other one PatientModel model object.
Both classes are quite similar:
class PatientDTO {
private String name;
private List<AddressDTO> address;
// Constructors, getters and setters
}
class PatientModel {
private String id;
private String nameElement;
private List<AddressModel> addressElement;
// Constructors, getters and setters
}
class AddressDTO {
private String city;
private String country;
private List<String> linesElement;
// Constructors, getters and setters
}
class AddressModel {
private String city;
private String countryElement;
private List<String> linesElement;
// Constructors, getters and setters
}
Main differences are:
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
Other issue I like to solve, is that:
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
I'd like to build an assertion like this:
PatientDTO patientDTO;
PatientModel patientModel;
assertThat(patientDTO).isEqual(patientModel);
Shortly:
Should I build a custom assertion?
Should I have an assertion for Address an other one for Patient containing previous Address assertion? How could I get this?
What aboud Address assertion for Patient, Organization
What I want to avoid is code like this:
assertThat(patientDTO).anySatisfy(p->{
assertThat(p.getName()).withFailMessage("expected name: "+ p.getAddress().getCity()).isEqualTo(patientModel.getNameElement());
assertThat(p.getAddress().getCity()).withFailMessage("expected city: "+ p.getAddress().getCity()).isEqualTo(patientModel.getCityElement());
assertThat(p.getAddress().getCountry()).withFailMessage("expected country: "+ p.getAddress().getCountry()).isEqualTo(patientModel.getCountryElement());
...
}
);
I want to avoid above code since Patient classes are really large. Here I've shorted them for clarity purpouses.
Any ideas?
The field-by-field recursive comparison could help for this purpose:
PatientDTO patientDTO = new PatientDTO(...);
PatientModel patientModel = new PatientModel(...);
assertThat(patientDTO).usingRecursiveComparison()
.isEqualTo(patientModel);
Some fields are not present on DTOs: PatientDTO.id doesn't exist.
There are a few methods that can be used to tune the comparison and ignore fields:
Directly with ignoringFields(String…​ fieldsToIgnore)
By regexes with ignoringFieldsMatchingRegexes(String…​ regexes)
By types with ignoringFieldsOfTypes(Class…​ typesToIgnore)
Some field names contains suffixes on Model classes: PatientDTO.name <> PatientModel.nameElement.
This is currently not supported and was also asked in https://stackoverflow.com/a/70381488/9714611. We plan to raise a feature request about it and I will update the answer once the issue link is ready.
Address related assertion should be shared. I mean, Address-like classes are present on other classes, for exemple, Organization, Practitioner...
If the target is always isEqualTo, probably a custom assertion implementation is not needed as long as the limitation of the recursive comparison about not being able to compare fields with different names is not a show-stopper. These fields would require ad-hoc comparison until a better solution is available.
If the target is to provide assertions in a domain-specific language, like:
assertThat(patientDTO).hasAddress(addressDTO);
then a custom assertion implementation can be added.
Also, there is an assertions generator with plugins for Maven and Gradle that can be used to generate assertions based on the class attributes.

Is there a JPA annotation equivalent of jackson's #JsonAnyGetter/#JsonAnySetter?

With jackson, I can use #JsonAnyGetter and #JsonAnySetter to serialize/deserialize a Map<String, Object> into extra fields of a json object. Is there a JPA annotation that will do similar things with extra db column values being get/set from/into a member Map?
Specifically, I'd like to use jooq's .fetchInto(Pojo.class) to hydrate a java object. I can manually use .fetch(RecordMapper<Record, Pojo>) to get the results I want by hydrating the Map member from the Record fields manually, but wondering if there's a more automatic way of doing this. Pojo code could look something like the following (use lombok's #Data to make it concise):
#Data
public class Pojo {
#Column("field1")
private int field1;
#Column("field2")
private String field2;
#JsonAnyGetter // works for json serialization,
#JsonAnySetter // is there an equivalent for JPA?
private Map<String, Object> extraFields;
}
You can register a RecordMapperProvider with your jOOQ configuration in order to override how various methods, including fetchInto(Class) apply mapping:
https://www.jooq.org/doc/3.11/manual/sql-execution/fetching/pojos-with-recordmapper-provider/

kotlin data class + bean validation jsr 303

I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project.
Given the following data class declarartion :
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#Size(min=5, max=15)
val name: String
)
The #Size annotation has no effect here, making me able to save a user with a name of 1 character.
It works well when executing the very same example but in a Java class instead of Kotlin.
This makes me think of a Kotlin problem.
Thanks in advance for you help !
You need to use Annotation use-site targets since the default for a property declared in the constructor is to target the annotation on the constructor parameter instead of the getter (which will be seen by JavaBeans compliant hosts) when there are multiple options available. Also using a data class might be inappropriate here (see note at end).
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#get:Size(min=5, max=15) // added annotation use-site target here
val name: String
)
The property target from the Kotlin docs may look tempting, but it can only be seen from Kotlin and not Java. Usually get does the trick, and it is not needed on the bean set.
The docs describe the process as:
If you don’t specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param
property
field
And the #Size annotation is:
#Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER})
Therefore since PARAMETER is a valid target, and multiple targets are available (parameter, field, method [get/set]) it choses PARAMETER which is not what you want. Therefore for a JavaBean host to see the property it will look for the getter (properties are defined by the getter/setter and not the backing field).
In one of the Java samples, it shows:
public class Book {
private String title;
private String description;
// ...
#NotEmpty(groups={FirstLevelCheck.class, Default.class})
#Size(max=30)
public String getTitle() {
return title;
}
// ...
}
Which matches our usage of having it on the getter. If it were to be on the field like some of the validation annotations show, see the field use-site target. Or if the field must also be publicly accessible, see the #JvmField annotation in Kotlin.
NOTE: As mentioned in notes from others, you should likely consider NOT using a data class for entities if they use an auto-generated ID since it will not exist for new objects the same as for retrieved objects; and a data class will generate equals and hashCode to include all fields including the ones it should not. You can read guidance about this from the Hibernate docs.
Use the #get or #field targets for validation annotations. Annotations with the target #param(first default) and #property are not supported.
e.g:
From #NotEmpty To #field:NotEmpty
data class Student(
#field:NotEmpty #field:Size(min= 2, message = "Invalid field") var name: String? = ""
)
GL
Jayson Minard
Annotation use site targets

A strange phenomenon when use dozer in jpa project,why Mapping annotation in lazy load object can't work?

I met a very strange phenomenon when using dozer in jpa project.
I have a UserSupplier object and a Supplier object.
UserSupplier:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "supplier_id", nullable = false)
private Supplier supplier;
In my code I first query a UserSupplier List, then convert it to SupplierList.
List<Supplier> supplierList = new ArrayList<>(usList.size());
usList.forEach(us -> supplierList.add(us.getSupplier()));
Then I convert SupplierList to SupplierView List and return it to Caller.
BeanMapper.mapList(supplierList, SupplierView.class);
My dozer configure in these objects like below
Supplier:
#Id
#GeneratedValue
#Mapping("supplierId")
private int id;
SupplierView:
private int supplierId;
Very funny, supplierId in SupplierView always 0(default int value),but other fileds can convert successfully, only id field fail. I don't why is this, why only id field can't convert to supplierId, but other fields could?
For above problem, there are below solutions
1. Change field name (supplierId to id):
Supplier:
// #Mapping("supplierId")
private int id;
SupplierView:
private int id;
but Caller(front-end) have to change code.
2. Change fetchType to eager:
UserSupplier:
#ManyToOne
private Supplier supplier;
After reading dozer documentation, I find some thing. After trying it, I got another solution.
That is add a dozer.properties into classpath, content inside is
org.dozer.util.DozerProxyResolver=org.dozer.util.HibernateProxyResolver
More detail please see
http://dozer.sourceforge.net/documentation/proxyhandling.html
This is probably because JPA uses proxy objects for lazy loading of single entity reference. Proxy object is effectively a subclass of your entity class. I guess that dozer can find #Mapping annotation only on fields declared in the class of given object, and not on fields defined in parent classes. Dozer project states that annotation mapping is experimental. Therefore it is possible that it does not cover mapping class hierarchies well.
I suggest to try configure mapping of supplierId by other means (XML, dozer mapping API) and see if it works. If all fails, you could write a custom MapperAware converter between Supplier and SupplierView. You would map source object to target object using supplied mapper, and finilize it by copying value of id to supplierId.

Wicket - Wrapped collection Model "transformation"

I have a domain object which has a collection of primitive values, which represent the primary keys of another domain object ("Person").
I have a Wicket component that takes IModel<List<Person>>, and allows you to view, remove, and add Persons to the list.
I would like to write a wrapper which implements IModel<List<Person>>, but which is backed by a PropertyModel<List<Long>> from the original domain object.
View-only is easy (Scala syntax for brevity):
class PersonModel(wrappedModel: IModel[List[Long]]) extends LoadableDetachableModel[List[Person]] {
#SpringBean dao: PersonDao =_
def load: List[Person] = {
// Returns a collection of Persons for each id
wrappedModel.getObject().map { id: Long =>
dao.getPerson(id)
}
}
}
But how might I write this to allow for adding and removing from the original List of Longs?
Or is a Model not the best place to do this translation?
Thanks!
You can do something like this:
class PersonModel extends Model<List<Person>> {
private transient List<Person> cache;
private IModel<List<String>> idModel;
public PersonModel( IModel<List<String>> idModel ) {
this.idModel = idModel;
}
public List<Person> getObject() {
if ( cache == null ) {
cache = convertIdsToPersons( idModel.getObject() );
return cache;
}
public void setObject( List<Person> ob ) {
cache = null;
idModel.setObject( convertPersonsToIds( ob ) );
}
}
This isn't very good code but it shows the general idea. One thing you need to consider is how this whole thing will be serialised between requests, you might be better off extending LoadableDetachableModel instead.
Another thing is the cache: it's there to avoid having to convert the list every time getObject() is called within a request. You may or may not need it in practice (depends on a lot of factors, including the speed of the conversion), but if you use it, it means that if something else is modifying the underlying collection, the changes may not be picked up by this model.
I'm not quite sure I understand your question and I don't understand the syntax of Scala.
But, to remove an entity from a list, you can provide a link that simply removes it using your dao. You must be using a repeater to populate your Person list so each repeater entry will have its own Model which can be passed to the deletion link.
Take a look at this Wicket example that uses a link with a repeater to select a contact. You just need to adapt it to delete your Person instead of selecting it.
As for modifying the original list of Longs, you can use the ListView.removeLink() method to get a link component that removes an entry from the backing list.