Use update mehtod mapping from other mapper - mapstruct

I am having two Mappers
#Mapper(uses= {ChildMapper.class})
public abstract class ParentMapper {
#Mappings...
public abstract Parent dtoToParent(
final ParentDto dto);
#InheritConfiguration(name = "dtoToParent")
public abstract Parent updateParentFromDto(
final ParentDto dto,
final #MappingTarget Parent parent);
}
#Mapper()
public abstract class ChildMapper {
Child dtoToChild(
final ChildDto dto);
Set<Child> dtosToChilds(
final Set<ChildDto> childs);
Child updateChildFromDto(
final ChildDto dto,
final #MappingTarget Child geo);
}
When generating the implementation, how do I instruct MapStruct to use updateChildFromDto method inside the the method updateParentFromDto. It is generating with dtosToChilds which in-turn uses dtoToChild method.
Reason, when object is present, I want to update the properties rather than creating new, while when not present create new.

How would MapStruct even (in a generic way) recognise whether the object is present in the destination set?
But nothing is blocking you to write your own implementation to do that.. Something like this:
#Mapper()
public abstract class ChildMapper {
abstract Child dtoToChild(ChildDto dto);
abstract Child updateChildFromDto( ChildDto dto, #MappingTarget Child geo);
Set<Child> dtosToChilds(final Set<ChildDto> childs);
// NOTE the void, I don't like to return a mapping target. MapStruct calls
// update methods from update methods an regular methods from regular methods
// using both in one signature, things tend to get blurry
void updateDtosToChilds(Set<ChildDto> childs, #MappingTarget Set<Child> target){
for ( ChildDto child : childs {
Child targetChild = null;
// get matching child from target
if ( targetChild != null ) {
updateChildFromDto( child, targetChild );
}
else {
target.add( dtoToChild( child ) );
}
}
}
}
and like indicated above, don't do:
#InheritConfiguration(name = "dtoToParent")
public abstract Parent updateParentFromDto(
final ParentDto dto,
final #MappingTarget Parent parent);
but rather
#InheritConfiguration(name = "dtoToParent")
public abstract void updateParentFromDto(
final ParentDto dto,
final #MappingTarget Parent parent);

Related

Mapping a field using existing target value (Mapstruct)

i have a custom case that some of my dto's have a field of type X, and i need to map this class to Y by using a spring service method call(i do a transactional db operation and return an instance of Y). But in this scenario i need to use existing value of Y field. Let me explain it by example.
// DTO
public class AnnualLeaveRequest {
private FileInfoDTO annualLeaveFile;
}
//ENTITY
public class AnnualLeave {
#OneToOne
private FileReference annualLeaveFile;
}
#Mapper
public abstract class FileMapper {
#Autowired
private FileReferenceService fileReferenceService;
public FileReference toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) {
return fileReferenceService.updateFile(fileInfoDTO, fileReference);
}
}
//ACTUAL ENTITY MAPPER
#Mapper(uses = {FileMapper.class})
public interface AnnualLeaveMapper {
void updateEntity(#MappingTarget AnnualLeave entity, AnnualLeaveRequest dto);
}
// WHAT IM TRYING TO ACHIEVE
#Component
public class MazeretIzinMapperImpl implements tr.gov.hmb.ikys.personel.izinbilgisi.mazeretizin.mapper.MazeretIzinMapper {
#Autowired
private FileMapper fileMapper;
#Override
public void updateEntity(AnnualLeave entity, AnnualLeaveUpdateRequest dto) {
entity.setAnnualLeaveFile(fileMapper.toFileReference(dto.getAnnualLeaveFile(), entity.getAnnualLeaveFile()));
}
}
But mapstruct ignores the result of "FileReference toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) " and does not map the result of it to the actual entity's FileReference field. Do you have any idea for resolving this problem?
Question
How do I replace the annualLeaveFile property while updating the AnnualLeave entity?
Answer
You can use expression to get this result. For example:
#Autowired
FileMapper fileMapper;
#Mapping( target = "annualLeaveFile", expression = "java(fileMapper.toFileReference(entity.getAnnualLeaveFile(), dto.getAnnualLeaveFile()))" )
abstract void updateEntity(#MappingTarget AnnualLeave entity, AnnualLeaveRequest dto);
MapStruct does not support this without expression usage. See the end of the Old analysis for why.
Alternative without expression
Instead of fixing it in the location where FileMapper is used, we fix it inside the FileMapper itself.
#Mapper
public abstract class FileMapper {
#Autowired
private FileReferenceService fileReferenceService;
public void toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) {
FileReference wanted = fileReferenceService.updateFile(fileInfoDTO, fileReference);
updateFileReference(fileReference, wanted);
}
// used to copy the content of the service one to the mapstruct one.
abstract void updateFileReference(#MappingTarget FileReference fileReferenceTarget, FileReference fileReferenceFromService);
}
Old analysis
The following is what I notice:
(Optional) your FileMapper class is not a MapStruct mapper. This can just be a normal class annotated with #Component, since it does not have any unimplemented abstract methods. (Does not affect code generation of the MazeretIzinMapper implementation)
(Optional, since you have this project wide configured) you do not have componentModel="spring" in your #Mapper definition, maybe you have this configured project wide, but that is not mentioned. (required for the #Autowired annotation, and #Component on implementations)
Without changing anything I already get a working result as you want it to be, but for non-update methods (not listed in your question, but was visible on the gitter page where you also requested help) the FileMapper as is will not be used. It requires an additional method that takes only 1 argument: public FileReference toFileReference(FileInfoDTO fileInfoDTO)
(Edit) to get rid of the else statement with null value handling you can add nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE to the #Mapper annotation.
I've run a test and with 1.5.0.Beta2 and 1.4.2.Final I get the following result with the thereafter listed FileMapper and MazeretIzinMapper classes.
Generated mapper implementation
#Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-03-11T18:01:30+0100",
comments = "version: 1.4.2.Final, compiler: Eclipse JDT (IDE) 1.4.50.v20210914-1429, environment: Java 17.0.1 (Azul Systems, Inc.)"
)
#Component
public class MazeretIzinMapperImpl implements MazeretIzinMapper {
#Autowired
private FileMapper fileMapper;
#Override
public AnnualLeave toEntity(AnnualLeaveRequest dto) {
if ( dto == null ) {
return null;
}
AnnualLeave annualLeave = new AnnualLeave();
annualLeave.setAnnualLeaveFile( fileMapper.toFileReference( dto.getAnnualLeaveFile() ) );
return annualLeave;
}
#Override
public void updateEntity(AnnualLeave entity, AnnualLeaveRequest dto) {
if ( dto == null ) {
return;
}
if ( dto.getAnnualLeaveFile() != null ) {
if ( entity.getAnnualLeaveFile() == null ) {
entity.setAnnualLeaveFile( new FileReference() );
}
fileMapper.toFileReference( entity.getAnnualLeaveFile(), dto.getAnnualLeaveFile() );
}
}
}
Source classes
Mapper
#Mapper( componentModel = "spring", uses = { FileMapper.class }, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE )
public interface MazeretIzinMapper {
AnnualLeave toEntity(AnnualLeaveRequest dto);
void updateEntity(#MappingTarget AnnualLeave entity, AnnualLeaveRequest dto);
}
FileMapper component
#Mapper
public abstract class FileMapper {
#Autowired
private FileReferenceService fileReferenceService;
public FileReference toFileReference(#MappingTarget FileReference fileReference, FileInfoDTO fileInfoDTO) {
return fileReferenceService.updateFile( fileInfoDTO, fileReference );
}
public FileReference toFileReference(FileInfoDTO fileInfoDTO) {
return toFileReference( new FileReference(), fileInfoDTO );
}
// other abstract methods for MapStruct mapper generation.
}
Why the exact wanted code will not be generated
When generating the mapping code MapStruct will use the most generic way to do this.
An update mapper has the following criteria:
The #MappingTarget annotated argument will always be updated.
It is allowed to have no return type.
the generic way to update a field is then as follows:
// check if source has the value.
if (source.getProperty() != null) {
// Since it is allowed to have a void method for update mappings the following steps are needed:
// check if the property exists in the target.
if (target.getProperty() == null) {
// if it does not have the value then create it.
target.setProperty( new TypeOfProperty() );
}
// now we know that target has the property so we can call the update method.
propertyUpdateMappingMethod( target.getProperty(), source.getProperty() );
// The arguments will match the order as specified in the other update method. in this case the #MappingTarget annotated argument is the first one.
} else {
// default behavior is to set the target property to null, you can influence this with nullValuePropertyMappingStrategy.
target.setProperty( null );
}

How to prevent creation of empty objects

I'm trying to map a model of a webservice where every List is inside nested object to something more simple.
Model 1
public class Parent {
private Children children;
}
public class Children {
private List<Child> children;
}
public class Child {
}
Model 2(simplified)
public class Parent2 {
private List<Child2> children;
}
public class Child {
}
The mapping is pretty straightforward:
#Mappings({#Mapping(source = "entity.children.children", target = "children")})
Parent2 parentToParent2(Parent entity);
#InheritInverseConfiguration
Parent parent2ToParent(Parent2 entity);
Mapping works fine except for one problem. When I map Parent with null children to Parent2 and back to Parent, the Children object is created with empty list. Is there some way to prevent that?
You can achieve that with both a mapper decorator or an AfterMapping hook.
Decorator
Decorator:
public abstract class MapperDecorator implements Mapper {
private final Mapper delegate;
public MapperDecorator(Mapper delegate) {
this.delegate = delegate;
}
#Override
public Parent parent2ToParent(Parent2 entity) {
Parent parent = delegate.parent2ToParent(entity);
if (entity.getChildren() == null) {
parent.setChildren(null);
}
return parent;
}
}
Mapper:
#org.mapstruct.Mapper
#DecoratedWith(MapperDecorator.class)
public interface Mapper {
#Mapping(source = "entity.children.children", target = "children")
Parent2 parentToParent2(Parent entity);
#InheritInverseConfiguration
Parent parent2ToParent(Parent2 entity);
Child2 childToChild2(Child entity);
Child child2ToChild(Child2 entity);
}
AfterMapping
Mapper:
#org.mapstruct.Mapper
public abstract class Mapper {
#Mapping(source = "entity.children.children", target = "children")
public abstract Parent2 parentToParent2(Parent entity);
#InheritInverseConfiguration
public abstract Parent parent2ToParent(Parent2 entity);
public abstract Child2 childToChild2(Child entity);
public abstract Child child2ToChild(Child2 entity);
#AfterMapping
public void afterParent2ToParent(Parent2 source,
#MappingTarget Parent target) {
if (source.getChildren() == null) {
target.setChildren(null);
}
}
}

GWTP get access from child presenter/view to parent presenter/view

I have 2 presenters/view. Lets call them parent and child. parent presenter is a container (using slot mechanism) for the child presenter.
In the child presenter's view user clicked button and I would like to handle this action in parent presenter.
How can I do this? What is the best approach? Should I use some kind of event and eventhandler? Or should I inject one presenter to the other?
Both are possible.
For events - GWTP have a simplified version of GWT events:
public interface MyEventHandler extends EventHandler {
void onMyEvent(MyEvent event);
}
public class MyEvent extends GwtEvent<MyEventHandler> {
public static Type<MyEventHandler> TYPE = new Type<MyEventHandler>();
private Object myData;
public Type<MyEventHandler> getAssociatedType() {
return TYPE;
}
protected void dispatch(MyEventHandler handler) {
handler.onMyEvent(this);
}
public MyEvent(Object myData) {
this.myData = myData;
}
/*The cool thing*/
public static void fire(HasHandlers source, Object myData){
source.fireEvent(new MyEvent(myData));
}
}
So in your child presenter you'll simply do:
MyEvent.fire(this, thatObjectYoudLikeToPass);
and to register it, in the parent, you'd either use:
addRegisteredHandler(MyEvent.TYPE, handler);
or
addVisibleHandler(MyEvent.TYPE, handler);
if you want it to be processed only when the parent is visible. I suggest you yo add these handlers in onBind method of your presenter (don't forget to call super.onBind() first when overriding)
For injection:
Simply make sure:
Your parent presenter is a singleton
To avoid circular dependency error in GIN do not wire it like
#Inject ParentPresenter presenter;
instead do it like this:
#Inject
Provider<ParentPresenter> presenterProvider;
and access it with presenterProvider.get() in your child

Same JPA callback method in #MappedSuperclass and child class

Can I define same JPA callback method in parent and child class as below? If yes, do I need to invoke super.onPrePersist(); in child class onPrePersist() method?
#MappedSuperclass
public abstract class AbstractEntity {
#PrePersist
protected onPrePersist() {
System.out.println("Parent onPrePersist() invoked");
}
}
#Entity
#Table(name = "child")
public class Child extends AbstractEntity {
#PrePersist
protected onPrePersist() {
**super.onPrePersist();**
System.out.println("Child onPrePersist() invoked");
}
}
I have written a unit test for the above scenario and It works. For each of the callback methods in child class, you have to invoke the parent callback method first:
#Override
#PrePersist
protected onPrePersist() {
**super.onPrePersist();**
System.out.println("Child onPrePersist() invoked");
}
You don't have to invoke the parent callback method by yourself, just don't override the #PrePersist annotated method as it hides the parent method and prevents it from being executed. If your callback methods have different names they will be invoked in the order according to their place in the hierarchy, most general classes first.
#MappedSuperclass
public abstract class AbstractEntity {
#PrePersist
protected onPrePersistParent() {
System.out.println("Parent onPrePersist() invoked");
}
}
#Entity
#Table(name = "child")
public class Child extends AbstractEntity {
#PrePersist
protected onPrePersistChild() {
System.out.println("Child onPrePersist() invoked");
}
}
This will produce the output:
Parent onPrePersist() invoked
Child onPrePersist() invoked

GWT CellTree Selection of Parent nodes

I'm trying to use the GWT CellTree to display a heterogeneous, hierarchical data model. I need to be able to a single selection, but be able to select Parent nodes as well as child nodes. For example, if you look at GWT's own example, you'll see that they only provide one selection model for the leave nodes.
I tried to extend their example by providing one selection model for all nodes. However, that seems impossible. So what I ended up with where 3 SelectionModels one for each node type (Composer, PlayList, Song).
What am I missing?
Thanks in advance.
In the getNodeInfo function of your TreeViewModel you have to pass the selectionModel to each new DefaultNodeInfo instance at each level.
return new DefaultNodeInfo<MyDTO>(dataProvider,new MyDTOCell(),selectionModel,null);
and then in the SelectionChangeEventHandler you have do something like this:
selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
#Override
public void onSelectionChange(SelectionChangeEvent event) {
Object object = selectionModel.getSelectedObject();
if (object instanceof MyRootDTO)
{
// DO SOMETHING with root level selected node
}
else if (object instanceof MySecondLevelDTO) {
// DO SOMETHING WITH 2. level selected node
}
// additional levels
});
Update:
In order to get around the typing problem, you can define an abstract base class which is extended by all your DTO's.
public abstract class BaseModel {
public static final ProvidesKey<BaseModel> KEY_PROVIDER = new ProvidesKey<BaseModel>() {
public Object getKey(BaseModel item) {
return item == null ? null : item.getId();
}
};
public abstract Object getId();
}
In your DTO's you extend the BaseModel and implement the abstract getId() method:
public class MyDTO extends BaseModel {
#Override
public Object getId() {
//return unique ID (i.e. MyDTO_1)
}
}