Currently I have these three classes:
#Value
#NonFinal
#SuperBuilder
public class Parent {
// Some fields
}
#Value
#EqualsAndHashCode(callSuper = true)
#SuperBuilder(toBuilder = true)
public class ChildA extends Parent {
// Some fields
}
#Value
#EqualsAndHashCode(callSuper = true)
#SuperBuilder(toBuilder = true)
public class ChildB extends Parent {
// Some fields
}
I want to use it in a mapper as follows to avoid duplicating any code:
private ChildA buildChildA(Entity entity) {
Parent parent = ((ChildB) buildParent(entity, ChildA.builder().build()))
.toBuilder()
// Populate Child A fields from entity
.build();
}
private ChildB buildChildB(Entity entity) {
Parent parent = ((ChildA) buildParent(entity, ChildA.builder().build()))
.toBuilder()
// Populate Child B fields from entity
.build();
}
private Parent buildParent(Partner entity, Parent parent) {
return parent.toBuilder()
// Populate Parent fields here
.build();
}
However when I try to compile I get:
ChildA.java:13: error: method does not override or implement a method from a supertype
#SuperBuilder(toBuilder = true)
^
ChildB.java:13: error: method does not override or implement a method from a supertype
#SuperBuilder(toBuilder = true)
^
2 errors
How do you use toBuilder with #SuperBuilder? I'm using lombok v1.18.4.
If you want to use #SuperBuilder with toBuilder, all classes in the hierarchy must have toBuilder=true. The reason is that the toBuilder() method only copies the field values from its respective class, but delegates the copying of the field values from the supertypes to the supertypes' toBuilder() methods.
So just add toBuilder=true to your Parent class, too.
Related
I have the following table class which makes usage of #Builder Lombok annotation with a custom builder class:
#Builder
public class MyTable {
...
public static class MyTableBuilder {
public void entry(final MyEntry myEntry) {
...
}
}
}
In another class that is composed by MyTable I would like to make usage of #Builder + #Singular annotations so that I can build TableOwner instances specifying entry by entry.
#Builder
public class TableOwner {
#Singular("entry")
private final MyTable entries;
}
TableOwner.builder()
.entry(...)
.entry(...)
.entry(...)
.build()
However the #Singular annotation on entries results the error "Lombok does not know how to create the singular-form builder methods for type 'MyTable'; they won't be generated.".
Is there a way I can point to MyTableBuilder#entry method as the singular handler for entries?
Given the following classes with the Lombok annotations #Data and #SuperBuilder
#Data
#SuperBuilder
public abstract class Parent {
protected final String userId;
protected final Instant requestingTime;
}
#Data
#SuperBuilder
public class Child extends Parent {
private final Instant beginningDate;
private final Instant endingDate;
private final Collection<String> fields;
}
I am getting the following error appearing over the #Data annotation in the Child class:
Implicit super constructor Parent() is undefined. Must explicitly invoke another constructor.
Is there a way to configure a non-default constructor on the Child class's #Data annotation in order to have all final fields on both the Child and Parent classes initialized when invoking the Builder?
I have tried a few different combinations of the #Data, #Getter, #Setter annotations with the #SuperBuilder annotation on both the child and parent classes, but haven't found a working solution yet. I am using Lombok 1.18.10.
For reference, this question is related
EDIT
This is effectively the constructor that Lombok should be constructing and invoking on the SuperBuilder.build() operation.
public Child(
final String userId,
final Instant requestingTime,
final Instant beginningDate,
final Instant endingDate,
final Collection<String> fields) {
super(userId, requestingTime);
this.beginningDate = beginningDate;
this.endingDate = endingDate;
this.fields= fields;
}
As requested, this is how I would expect to invoke the builder on the Child object.
final Child child = Child.Builder()
.userId(<value>)
.requestingTime(<value>)
.beginningDate(<value>)
.endingDate(<value>)
.fields(<value>)
.build();
AFAIK, #Data generates a #NoArgsConstructor, which is just wrong. Actually, #Data is wrong per se, as it's meant for mutable classes; #Value would be better, but it can't deal with the super constructor either.
So remove #Data, add #Getter, #EqualsAndHashCode, #ToString and whatever you need. Don't forget to add callSuper=true in the subclass.
This is effectively the constructor that Lombok should be constructing and invoking on the SuperBuilder.build() operation.
public Child(
final String userId,
final Instant requestingTime,
final Instant beginningDate,
final Instant endingDate,
final Collection<String> fields) {
super(userId, requestingTime);
this.beginningDate = beginningDate;
this.endingDate = endingDate;
this.fields= fields;
}
No, that's not how SuperBuilder works. This is actually Lombok can't do as it can't see the super fields. Instead, the builder uses something like
public Child(ChildBuilder b) {
super(b);
this.beginningDate = b.beginningDate;
this.endingDate = b.endingDate;
this.fields= b.fields;
}
You can believe what Jan Rieke says, he wrote it.
#Data annotation implicitly generate code for below mentioned functionalities:
setter
getter
toString
equallAndHashCode
constructor(for required arguments only)
It means constructor declaration of loombok will generate code for Parent class will be as mentioned below:
Person(String userId, Instant requestingTime)
Similarly for Child class:
Child(Instant beginningDate, Instant endingDate, Collection fields)
Now as your program is throwing exception that
Parent()
is undefined in parent class.
Please annotate your class with :
#NoArgsConstructor
This will generate required default constructor.
I have this class
public class Hostel extends Hotel<Book> {
}
and this other one
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(of = { "id" })
#SuperBuilder(toBuilder = true)
#JsonInclude(NON_NULL)
public class Hotel<T> {
...
}
but when I do
Hostel hostel = Hostel.builder().build();
I got this compilation error
Required type: Hostel
Provided:
capture of ?
You don't have any annotations on Hostel. Hostel.builder() is really a masquerading Hotel.builder().
So the assignment would have to be
final Hotel<?> build = Hostel.builder().build();
Or more accurately (making static methods subject to inheritance was IMO a mistake)
final Hotel<?> build = Hotel.builder().build();
You probably want to add some Lombok annotations to the child class.
TL;DR
I want to use modelMapper in a way that I map from AbstractParent to AbstractParentDTO and later in the ModelMapper-Config call the specific mappers for each Sub-class and then skip the rest of the (abstrac-class) mappings.
How is that Possible? Is this the right approach? Is there a design flaw?
What I have:
The parent entity:
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "type")
public abstract class Parent {
//some more fields
}
One child entity:
//Basic Lombok Annotations
#DiscriminatorValue("child_a")
public class ChildA extends Parent {
//some more fields
}
Another child entity:
#DiscriminatorValue("child_b")
public class ChildB extends Parent {
//some more fields
}
Then I have the parent DTO class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = ChildA.class, name = "child_a"),
#JsonSubTypes.Type(value = ChildB.class, name = "child_b"),
public abstract class ParentDTO {
//some more fields
}
One Child DTO:
public class ClassADTO extends ParentDTO {
//some more fields
}
and another DTO:
public class ClassBDTO extends ParentDTO {
//some more fields
}
In my case I'll get DTO's from the controller and map them to Entities when giving them to the Service. I'll have to do the same thing in 5-6 Endpoints.
The Endpoints look roughly like this:
#PreAuthorize(CAN_WRITE)
#PutMapping("/{id}")
public ResponseEntity<ParentDTO> update(
#PathVariable("id") UUID id,
#RequestBody #Valid ParentDTO parentDTO) {
Parent parent = parentService.update(id, parentDTO);
if (parentDTO instanceof ChildADTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildADTO.class));
} else if (parentDTO instanceof ChildBDTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildBDTO.class));
}
throw new BadRequestException("The Parent is not Valid");
}
Only that I have a few more Childs that make things even bulkier.
What I want:
Instead of checking a bunch of times what instance the DTO (or Entity) is, I simply want to write for example:
modelmapper.map(parent, ParentDTO.class)
and do the "instance of..." check ONCE in my ModelMapper Configuration.
What I've tried:
I already have different Converters for every possible direction and mapping-case defined in my ModelMapper Configuration (since they require more complex mapping anyways).
I've tried to solve my problem by writing one more Converter for the Parent Classes and setting it as a ModelMapper PreConverter:
//from Entity to DTO
Converter<Parent, ParentDTO> parentParentDTOConverter = mappingContext -> {
Parent source = mappingContext.getSource();
ParentDTO dest = mappingContext.getDestination();
if (source instanceof CHildA) {
return modelMapper.map(dest, ChildADTO.class);
} else if (source instanceof ChildB) {
return modelMapper.map(dest, ChildBDTO.class);
}
return null;
};
and:
modelMapper.createTypeMap(Parent.class, ParentDTO.class)
.setPreConverter(parentParentDTOConverter);
But I'm always getting the same MappingError:
1) Failed to instantiate instance of destination
com.myexample.data.dto.ParentDTO. Ensure that
com.myexample.data.dto.ParentDTOO has a non-private no-argument
constructor.
which I get (I guess), I cannot construct an Object of an abstract class. But thats not what I'm trying, am I?
I guess that modelMapper is still doing the rest of the Mapping after finishing with my PreConverter. I've also tried to set it with .setConverter but always with the same result.
Does anyone knows how to 'disable' the custom mappings? I don't
really want to write "pseudo-mappers" that act like mappers and just
call the specific mappers for each scenario.
Is my design just bad? How would you improve it?
Is this just not implemented into ModelMapper yet?
Any help and hint is appreciated.
Well, the solution I found uses converters. In this case modelMapper doesn't try to create a new instance of abstract class, but uses the converter directly.
You can put all the converters in same place
modelMapper.createTypeMap(ChildA.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassADTO.class));
modelMapper.createTypeMap(ChildB.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassBDTO.class));
....
I would use ObjectMapper instead of ModelMapper.
In Parent class add the possibility to get the discriminator value.
//..
public class Parent {
#Column(name = "type", insertable = false, updatable = false)
private String type;
//getters and setters
}
Your ParentDTO should be mapped to Child(*)DTO
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = ChildADTO.class, name = "child_a"),
#JsonSubTypes.Type(value = ChildBDTO.class, name = "child_b")
})
public abstract class ParentDTO {
// ..
}
in the conversion service/method add an object mapper with ignore unknown (to ignore what you did not declare in your DTO class)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
just simply call :
Parent parent = // get from repository
ParentDTO parentDTO = objectMapper.readValue(objectMapper.writeValueAsBytes(parent), ParentDTO.class);
In this way, your ParentDTO is always instantiated with the right type.
How about
TypeMap<Parent.class, ParentDTO.class> typeMap = modelMapper.createTypeMap(Parent.class, ParentDTO.class);
typeMap
.include(ChildA .class, ClassADTO .class)
.include(ChildB.class, ClassbDTO.class);
reference :http://modelmapper.org/user-manual/type-map-inheritance
I have a bunch of JAXB annotated classes that have a field in common, so I moved that field to a super class, like this
public class Base {
protected SomeType commonField;
}
#XmlRootElement(name = "foo") #XmlType(propOrder = { "commonField", "fooField" })
public class Foo extends Base {
private SomeOtherType fooField;
}
#XmlRootElement(name = "bar") #XmlType(propOrder = { "commonField", "barField" })
public class Bar extends Base {
private SomeOtherType barField;
}
Now whenever I marshall one of Foo or Bar I get an IllegalAnnotationException complaining about commonField being listed in propOrder but not present in the class. Removing it from the propOrder annotation everything works fine, but I thougt I was supposed to list all of the mapped fields. What am I missing?
The fields/properties from the inherited class will always appear before the fields/properties on the child classes. This means that by default you can not specify them in the propOrder on the child type. If however you mark the parent class as #XmlTransient the fields/properties will be treated as belonging to the child classes and can be included in the propOrder.
http://bdoughan.blogspot.com/2011/06/ignoring-inheritance-with-xmltransient.html