MapStruct specifie sub mapping - mapstruct

This is my example.
public class Company {
....
private String companyName;
....
}
public class Employee {
....
private String name;
....
}
public class EmployeeDto {
....
private String name;
private String companyName;
....
}
#Mapper(componentModel = "spring")
public interface EmployeeDtoMapper {
#Mapping(target = "name", source = "source1.name")
#Mapping(target = "companyName", source = "source2.companyName")
EmployeeDto toDto(Employee source1, Company source2);
List<EmployeeDto> toDtos(List<Employee> sources, #Context Company source2);
}
I want that the method toDtos use toDto to map Employee to EmployeeDto but mapstruct generate a employeeDtoToEmployeeDto method.
How can I fix it ?
thanks

Mapstruct only allows for selecting submappings based on 1 source and 1 target. Hence the #Context annotation. This will effectively ignore that parameter, only handed it down to submapping..
But.. you can tackle your problem like this..
#Mapper(componentModel = "spring")
public interface EmployeeDtoMapper {
#Mapping(target = "name", source = "source1.name")
EmployeeDto toDto(Employee source1, #Context Company source2);
#AfterMapping
default afterToDto(#MappingTarget EmployeeDto target, #Context Company source2) {
target.setCompanyName( source2.getCompanyName();
}
List<EmployeeDto> toDtos(List<Employee> sources, #Context Company source2);
}
Note if you have multiple parameters in mapping source2, and you like to use MapStruct for that as well, you can define a new interface method mapping EmployDTO toDo(Company source) and call that from your default method.

Related

Mapstruct: aftermapping with parameters

Here my code:
#Mapping(target = "auditoriaMetas", qualifiedByName = "sdf")
public abstract Auditoria mapToModificacio(QdCF qdcf, QdCFPresenter qdcfPresenter, Integer idInstrument);
#Named("sdf")
public List<AuditoriaMeta> mapToMetas(QdCF current, #Context QdCFPresenter incoming) {
return null;
}
I want that after mapToModificatio is performed, mapToMetas is also executed.
Above code doesn't perform.
Any ideas?
Mapstruct will not consider putting a normal value into one that is annotated with #Context. Therefor if you mark something with #Context, then it should be marked like that through the entire chain of calls.
For example:
#Mapping(target = "auditoriaMetas", source=".", qualifiedByName = "sdf")
public abstract Auditoria mapToModificacio(QdCF qdcf, #Context QdCFPresenter qdcfPresenter, Integer idInstrument);
#Named("sdf")
public List<AuditoriaMeta> mapToMetas(QdCF current, #Context QdCFPresenter incoming) {
return null;
}

InvalidDefinitionException: Cannot construct instance of `com.vehicle.datatransferobject.VehicleDTO`

In the REST endpoint I'm building in Spring Boot, I'm trying to pass my vehicleDTO to my controller. But before it reaches my controller, there is an error.
InvalidDefinitionException: Cannot construct instance of
com.vehicle.datatransferobject.VehicleDTO (no Creators, like default
construct, exist): cannot deserialize from Object value (no delegate-
or property-based Creator)
vehicleDTO
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.myvehicle.EngineType;
#JsonInclude(JsonInclude.Include.NON_NULL)
public class VehicleDTO {
#JsonIgnore
private Long id;
#NotNull(message = "vehiclenumber can not be null!")
private String vehiclenumber;
#Min(2)
#NotNull(message = "Seat count can not be less than 2!")
private Integer vehicleseatcount;
#NotNull(message = "Engine Type can not be null!")
private EngineType enginetype;
#Max(5)
private Integer vehiclerating;
private VehicleDTO(Long id, String vehiclenumber, Integer vehicleseatcount, EngineType enginetype,Integer vehiclerating){
this.vehiclenumber=vehiclenumber;
this.vehicleseatcount=vehicleseatcount;
this.enginetype=enginetype;
this.vehiclerating=vehiclerating;
this.id=id;
}
public static VehicleDTOBuilder newBuilder()
{
return new VehicleDTOBuilder();
}
#JsonProperty
public Long getId() {
return id;
}
public String getvehiclenumber() {
return vehiclenumber;
}
public Integer getvehicleseatcount() {
return vehicleseatcount;
}
public EngineType getEnginetype() {
return enginetype;
}
public Integer getvehiclerating() {
return vehiclerating;
}
public static class VehicleDTOBuilder{
private Long id;
private String vehiclenumber;
private Integer vehicleseatcount;
private EngineType enginetype;
private Integer vehiclerating;
public VehicleDTOBuilder setId(Long id) {
this.id = id;
return this;
}
public VehicleDTOBuilder setvehiclenumber(String vehiclenumber) {
this.vehiclenumber = vehiclenumber;
return this;
}
public VehicleDTOBuilder setvehicleseatcount(Integer vehicleseatcount) {
this.vehicleseatcount = vehicleseatcount;
return this;
}
public VehicleDTOBuilder setEnginetype(EngineType enginetype) {
this.enginetype = enginetype;
return this;
}
public VehicleDTOBuilder setvehiclerating(Integer vehiclerating) {
this.vehiclerating = vehiclerating;
return this;
}
public VehicleDTO createVehicleDTO()
{
return new VehicleDTO(id, vehiclenumber, vehicleseatcount, enginetype,vehiclerating);
}
}
}
My DTO has an Enum type called EngineType
public enum EngineType {
ELECTRIC, DIESEL
}
My controller looks like this
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public VehicleDTO addvehicle(#Valid #RequestBody VehicleDTO vehicleDTO)
{
VehicleDO vehicleDO = Mapper.VehicleDO(vehicleDTO);
return Mapper.makeVehicleDTO(Service.addvehicle(vehicleDO));
}
This exception :
InvalidDefinitionException: Cannot construct instance of
com.vehicle.datatransferobject.VehicleDTO (no Creators, like default
construct, exist): cannot deserialize from Object value (no delegate-
or property-based Creator)
means that Jackson didn't find a way to instantiate VehicleDTO that is the default constructor (no arg constructor) or a JsonCreator.
As you use a builder pattern you will configure the VehicleDTO class to make Jackson to instantiate VehicleDTO with the VehicleDTOBuilder such as :
#JsonDeserialize(builder = VehicleDTO.VehicleDTOBuilder.class)
public class VehicleDTO {
...
}
And annotate your builder with JsonPOJOBuilder as :
#JsonPOJOBuilder(buildMethodName = "createVehicleDTO", withPrefix = "set")
public static class VehicleDTOBuilder{
...
}
According to the javadoc, JsonPOJOBuilder is :
used to configure details of a Builder class: instances of which are
used as Builders for deserialized POJO values, instead of POJOs being
instantiated using constructors or factory methods. Note that this
annotation is NOT used to define what is the Builder class for a POJO:
rather, this is determined by JsonDeserialize.builder() property of
JsonDeserialize.
I faced this error when I used Lombok's #Builder and #Data annotations together on a POJO class that is used for connecting to an API (either for consuming or for providing response)
I removed the #Builder annotation and then it is working fine
In my case:
InvalidDefinitionException: Cannot construct instance of com.vehicle.datatransferobject.VehicleDTO (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
for the above exception, I just write Default Constructor which instantiates class and solved the problem.
Default Constructor:
public VehicleDTO() {
super();
// TODO Auto-generated constructor stub
}
If you are using Lombok - the best thing is to add these annotations to your DTO:
#AllArgsConstructor
#RequiredArgsConstructor
#Data
#Builder (optional)
In addition to davidxxx`s answer. I used Lombok. And in my case it looked like this:
#Data
#JsonDeserialize(builder = SomeClass.SomeClassBuilder.class)
#Builder(builderClassName = "SomeClassBuilder")
public class SomeClass {
// ...
#JsonPOJOBuilder(withPrefix = "")
public static class SomeClassBuilder {
}
}

How to reference an entity with inheritance in Spring Data REST when POSTing new entity?

I have entities with joined inheritance:
Supporter
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "supporterType")
#JsonSubTypes({
#JsonSubTypes.Type(value = PersonSupporterEntity.class, name = "PERSON"),
#JsonSubTypes.Type(value = CompanySupporterEntity.class, name = "COMPANY")
})
#DiscriminatorColumn(name="supporter_type")
#Table(name = "supporter")
public class SupporterEntity extends UpdatableEntity {
private long id;
private SupporterType supporterType;
private PartnerEntity partner;
...
}
PersonSupporter
#Entity
#DiscriminatorValue("PERSON")
#Table(name = "person_supporter")
public class PersonSupporterEntity extends SupporterEntity {
...
}
CompanySupporter
#Entity
#DiscriminatorValue("COMPANY")
#Table(name = "company_supporter")
public class CompanySupporterEntity extends SupporterEntity {
...
}
I have another entity which references SupporterEntity
#Entity
#Table(name = "contact")
public class ContactEntity extends UpdatableEntity {
private long id;
private SupporterEntity supporter;
...
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
public SupporterEntity getSupporter() {
return supporter;
}
...
}
Repositories
#Transactional
#RepositoryRestResource(collectionResourceRel = "supporters", path = "supporters")
public interface SupporterEntityRepository extends JpaRepository<SupporterEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-partner", rel = "by-partner")
public Page<SupporterEntity> findByPartnerName(#Param("name") String name, Pageable pageable);
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "person_supporters", path = "person_supporters")
public interface PersonSupporterEntityRepository extends JpaRepository<PersonSupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "company_supporters", path = "company_supporters")
public interface CompanySupporterEntityRepository extends JpaRepository<CompanySupporterEntity, Long> {
}
#Transactional
#RepositoryRestResource(collectionResourceRel = "contacts", path = "contacts")
public interface ContactEntityRepository extends JpaRepository<ContactEntity, Long> {
#Transactional(readOnly = true)
#RestResource(path = "by-supporter", rel = "by-supporter")
public ContactEntity findBySupporterId(#Param("id") Long id);
}
I use Spring Boot, Spring Data REST, Spring Data JPA, Hibernate, Jackson. When I try to create a new ContactEntity with a post request like this:
{
"supporter":"/supporters/52",
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
I get this exception:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_STRING), expected FIELD_NAME: missing property 'supporterType' that is to contain type id (for class com.facer.domain.supporter.SupporterEntity)
at [Source: HttpInputOverHTTP#4321c221; line: 1, column: 2] (through reference chain: com.facer.domain.supporter.ContactEntity["supporter"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.4.4.jar:2.4.4]
After 2 days of debugging I found a way, but I kinda guessed it. So if I post it like this:
{
"supporter":{
"supporterType":"PERSON",
"id":"52"
},
"postcode":"1111",
"city":"Test City 1",
"address":"Test Address 1",
"email":"test1#email.com",
"newsletter":true
}
It works, but I don't know why. What's wrong with the other request? It works like that everywhere else when the referenced entity does not have inheritance.
Just another workaround using a RelProvider:
Do not use #JsonTypeInfo
Create a RelProvider for SupporterEntity sub-classes
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SupporterEntityRelProvider implements RelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return "supporters";
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return "supporter";
}
#Override
public boolean supports(final Class<?> delimiter) {
return org.apache.commons.lang3.ClassUtils.isAssignable(delimiter, SupporterEntity.class);
}
}
See also:
https://jira.spring.io/browse/DATAREST-344
http://docs.spring.io/spring-hateoas/docs/current/reference/html/#configuration.at-enable
It looks like a Jackson problem. To be specific, it's the following code in com.fasterxml.jackson.databind.deser.SettableBeanProperty:
if (_valueTypeDeserializer != null) {
return _valueDeserializer.deserializeWithType(jp, ctxt, _valueTypeDeserializer);
}
return _valueDeserializer.deserialize(jp, ctxt);
Without inheritance _valueDeserializer.deserialize would be called which in turn runs some Spring code to convert the URI to a Supporter.
With inheritance _valueDeserializer.deserializeWithType is called and vanilla Jackson, of course, expects an object, not a URI.
If supporter was nullable you could first POST to /contacts and then PUT the supporter's URI to /contacts/xx/supporter. Unfortunately I am not aware of any other solution.
You should be able to workaround this by setting #JsonTypeInfo(use= JsonTypeInfo.Id.NONE) at the property/method level e.g.
Try with this:
#ManyToOne // same error with #OneToOne
#JoinColumn(name = "supporter_id", referencedColumnName = "id", nullable = false)
#JsonTypeInfo(use= JsonTypeInfo.Id.NONE)
public SupporterEntity getSupporter() {
return supporter;
}

TomEE Resteasy JAX-B -> Can not get Nested Object

I'm working on a RestWebService using Resteasy. The basic implementation works fine. Know I tried to return a Complexer- Object through rest...
Actually its pretty easy..I thought. I'm getting a problem because of my nested object (Address)...
What I try is this:
#XmlRootElement(name = "person")
#XmlAccessorType(XmlAccessType.FIELD)
public class Person implements Serializable {
private static final long serialVersionUID = 1199647317278849602L;
private String uri;
private String vName;
private String nName;
private Address address;
.....
#XmlElementWrapper(name="Former-User-Ids")
#XmlElement(name="Adress")
public Address getAddress() {
return address;
}
....
Address looks like this:
#XmlRootElement(name = "address")
#XmlAccessorType(XmlAccessType.FIELD)
public class Address {
private String uri;
private String street;
private String city;
public String getCity() {
return city;
}
public String getStreet() {
return street;
}
....
The Restservice looks like this. It worked perfect without the address object..
#Path("/getPersonXML/{personNumber}")
#GET
#Produces(MediaType.APPLICATION_XML)
public Patient getPatientXML(#PathParam("personNumber") String personNumber) throws ParseException {
Address a1 = new Address("de.person/address/" + "432432","Teststret12","TestCity", "32433", "TestCountry", "081511833");
Patient p1 = new Person();
p1.setAddress(a1);
p1.setUri("de.spironto/person/"+ "432432");
p1.setnName("Power");
p1.setvName("Max");
return p1;
}
At the moment I'm always getting a
javax.xml.bind.JAXBException:
Any Ideas?
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
PROBLEM
The #XmlElementWrapper annotation must be used with a collection property. This means you can have:
#XmlElementWrapper
public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
But not
#XmlElementWrapper
public Address getAddress() {
return address;
}
SOLUTION #1 - Using Any JAXB Proivder
You could use an XmlAdapter to accomplish this (see linked answer below):
Access attribute of internal element in the most simple way
SOLUTION #2 - Using EclipseLink JAXB (MOXy)
You could leverage the #XmlPath extension to map this use case:
#XmlPath("Former-User-Ids/Address")
public Address getAddress() {
return address;
}
For More Information
http://blog.bdoughan.com/2010/07/xpath-based-mapping.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
After building a small marshaller test. I got the failure that there are several properties with the same name. So I tried to delete all #XML_Eleemets annotations in the Address class.
That worked for me...

JAXB works with JPA, throw IllegalAnnotationsException

I have been stacked in this place for few days. any help will be appreciated.
Here is my story:
I have a JPA entity class (ExtOffer), and Now I annotate it with JAXB annotation in order to do marshall/unmarshall by JAXB. And I also create a wrapper class(ExtOffers), which basically is a collection of ExtOffer.
And when I call JAXBContext.newInstance(ExtOffers.class), I got an IllegalAnnotationsException:JAXB annotation is placed on a method that is not a JAXB property.
I search google and some post says that it's due to annotate #XmlElement on wrong place.
But my class has #XmlAccessorType(XmlAccessType.NONE) annotation and only the getter method has been annotated with #Xmlelement.
below is my ExtOffer class and ExtOffers class:
//ExtOffer:
#Entity
#Table (name = "extoffer")
#XmlType(name = "ExtOfferType")
#XmlAccessorType(XmlAccessType.NONE)
public class ExtOffer {
public ExtOffer() {
}
#Id
#Column(name = "OfferID", nullable = false, unique = true, length = 32)
protected String offerId;
#Column(name = "HasMoreScreenShot", nullable = false, unique = false, length = 1)
private String hasMoreScreenShot;
public void setOfferId(String offerId) {
this.offerId = offerId;
}
#XmlElement(name="OfferID", required = true)
public String getOfferId() {
return offerId;
}
public void setHasMoreScreenShot(String hasMoreScreenShot) {
this.hasMoreScreenShot= hasMoreScreenShot;
}
#XmlElement(name = "HasMoreScreenShot")
public String GetHasMoreScreenShot() {
return hasMoreScreenShot;
}
}
//ExtOffers wrapper
#XmlRootElement(name="extoffers")
#XmlAccessorType(XmlAccessType.NONE)
public class ExtOfferWrapper {
private List<ExtOffer> extoffers;
public ExtOfferWrapper() {
}
#XmlElement(name="extoffer")
public List<ExtOffer> getExtoffers() {
return extoffers;
}
public void setExtoffers(List<ExtOffer> extoffers) {
this.extoffers = extoffers;
}
}
JAXB annotation is placed on a method that is not a JAXB property
this problem is related to the following location:
at #javax.xml.bind.annotation.XmlElement(nillable=false, name=HasMoreScreenShot, required=false, defaultValue=, type=class javax.xml.bind.annotation.XmlElement$DEFAULT, namespace=##default)
at com.symbio.fuhu.appstore.jpa.entity.ExtOffer
at public java.util.List com.symbio.fuhu.appstore.jaxb.mapping.wrapper.ExtOfferWrapper.getExtoffers()
at com.symbio.fuhu.appstore.jaxb.mapping.wrapper.ExtOfferWrapper
You have an upper case 'G'
#XmlElement(name = "HasMoreScreenShot")
public String GetHasMoreScreenShot() {
return hasMoreScreenShot;
}