I'm having trouble using Mapstruct.
I am using a #Mapper annotated interface with #AfterMapping inside like follow:
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ConfiguracionReautorizacionMapper {
ConfiguracionReautorizacionDTO toConfiguracionReautorizacionDTO(final ConfiguracionReautorizacion configuracionReautorizacion);
ConfiguracionReautorizacion toConfiguracionReautorizacion(final ConfiguracionReautorizacionDTO configuracionReautorizacionDTO);
#AfterMapping
default void fillServiciosAsociados(#MappingTarget final ConfiguracionReautorizacionDTO configuracionReautorizacionDTO, final ConfiguracionReautorizacion configuracionReautorizacion) {
configuracionReautorizacionDTO.setTieneRolesOServiciosAsociados(!(CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk()) && CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk())));
}
}
The mapper works perfectly but the #AfterMappingmethod is never called.
I read other post that shows examples using abstract class instead of interface.
Is using abstract class mandatory for use #AfterMapping annotation?
You can't pass the object (that is assumed to be immutable). You should pass the builder.. like this:
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ConfiguracionReautorizacionMapper {
ConfiguracionReautorizacionDTO toConfiguracionReautorizacionDTO(final ConfiguracionReautorizacion configuracionReautorizacion);
ConfiguracionReautorizacion toConfiguracionReautorizacion(final ConfiguracionReautorizacionDTO configuracionReautorizacionDTO);
#AfterMapping
default void fillServiciosAsociados(#MappingTarget final ConfiguracionReautorizacionDTO.Builder configuracionReautorizacionDTO, final ConfiguracionReautorizacion configuracionReautorizacion) {
configuracionReautorizacionDTO.setTieneRolesOServiciosAsociados(!(CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk()) && CollectionUtils.isEmpty(configuracionReautorizacion.getRolesAplicacionEdesk())));
}
}
Checkout out MapStruct issue 1556.. You can also disable builders from 1.3.1 onwards
It is a bug when using Lombok##Builder and Mapstruct#AfterMapping
Please use this situation #BeanMapping(builder = #Builder(disableBuilder = true)).
see https://github.com/mapstruct/mapstruct/issues/1556#issuecomment-1011493330
When you are using Lombok builder and mapstruct together. #AfterMapping is not much useful though you pass a Builder object, since we can't retrieve the processed values. Instead I have used the custom method in my mapper to resolve this issue.
#Mapping(target ="field", expression = "java(customMethod(obj))")
this solved my use case, hope it helps others too.
As mentioned in the comments the issue is because you are likely using Lombok Builder annotation for ConfiguracionReautorizacionDTO and ConfiguracionReautorizacion and possibly any of the nested classes of these classes. AfterMapping functionality does not play well with Lombok Builder. Again Lombok generally works with MapStruct, it seems Builder annotation is a specific issue. See if you can create the classes without using Builder annotation and then AfterMapping functionality should work as expected.
Thanks to #Sjaak answer which is the legit answer. For my case this worked:
myAfterMapping(...,#MappingTarget MyBean.MyBeanBuilder<?, ?> myBean,...){
myBean.id(someValue);
}
MyBeanBuilder is autogenerated (#Builder).
This is a comment but can't fit in the comment section.
If someone is gonna use #raji's answer, you must pay attention to the name of the variable you pass to customMethod
instead of obj you must put the name of the variable in your mapper method.
for Example:
#Mapping(target ="name", expression = "java(resolveName(userModel))")
UserDTO toDTO(User userModel);
default String resolveName(final User model) {
return String.format("%s %s", model.firstName, model.lastName);
}
Related
I am trying to add checker framework's Nullness checker to our project, however I am having problem with our MapStruct converters.
Converter example
Lets say I have a converter from User to UserDto like following:
#MapperConfig(componentModel = SPRING)
public interface UserToUserDtoConverter
extends org.springframework.core.convert.converter.Converter<User, UserDto> {
}
Which generates the following implementation:
#Override
public UserDto convert(User source) {
if ( source == null ) {
return null;
}
UserDtoBuilder<?, ?> userDto = UserDto.builder();
userDto.id(source.getId());
return userDto.build();
}
Problem
Now the problem is that Checker framework complains about the return null;, as the implementation does not have #Nullable above the convert method.
Another problem is when the converter uses other converters that are autowired here, which results in initialization.field.uninitialized error.
Things I have tried
Now I know that I could simply ignore converters completely with -AskipDefs, however I would still like it to let it check that there won't be a problem with assigning #Nullable value from User to #NonNull value in UserDto (and vice versa, which could leave a hole in the project).
Another solution that came to my mind was adding #SuppressWarning annotation for these error codes in the converter interface, however mapstruct is not capable of propagating any annotation to the implementation if I am not mistaken mapstruct-issues.
Stub files won't help here either.
Is here some kind of solution for handling the generated code?
Is it possible to set a value from a field from the application.properties file?
I am looking for something like
#Mapping(target="version", expression="${application.version}")
StateDto stateToStateDto(State state);
where application.version=v1 is from the application.properties file.
Consider a "util service" like:
#Service
public class PropertyService {
#org.springframework.beans.factory.annotation.Value("${application.version}")
private String appVersion;
// accessors, more properties/stuff..
}
Then you can define your Mapping like:
#Mapper(// ..., ...
componentModel = "spring")
public abstract class StateMapper {
#Autowired
protected PropertyService myService;
#org.mapstruct.Mapping(target="version", expression="java(myService.getAppVersion())")
public abstract StateDto stateToStateDto(State state);
// ...
}
See also:
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
Mapstruct Expressions
My minimal solution #github
As far as my knowledge goes, this is not possible. Mapstruct analyses the #Mapping annotation in compile time. And the annotation parameters require constants. So getting them from a file would not be possible.
You can always implement something in MapStruct that fulfills your needs. But I would go with a simple self-implemented mapper where you take the value from your version field in runtime from the environment.
This is not possible through MapStruct. However, a feature could be raised that would support some custom expression language that would use Spring #Value and inject that.
e.g.
#Mapping(target="version", expression="springValue(${application.version})")
StateDto stateToStateDto(State state);
and then MapStruct will generate something like:
#Component
public class StateMapperImpl {
#Value("${application.version}")
private String version;
// ...
}
i'm using mapstruct for converting an object to another.
Into the object to convert, there's an interface, and mapstruct doesn't like that.
I was able to convert an interface to an object by implementing the default of the method and specifing the implementation to call:
public default MessagesList interfaceMapping (Integer not, List<MessageEntity> list) {
return messToImpl(numNotification, list);
}
Now the problem is that i don't know how to do a similar thing that is not a workaround, to convert an internal object signed as interface.
Just find out a good way to implement a custom code for a single object mapping:
#Mapping(target = "sender", expression = "java(new YourClass(null, messageEntity.getSenderType(), messageEntity.getSenderID(), messageEntity.getSenderContact()))")
In this way, through the expression you can define a custom code still using mapstruct definitions.
Just in case you could need to import a class not defined as source or target, just remember to annotate the class as following, to allow mapstruct to import the required class:
#Mapper(imports = YourClass.class)
I wrote a mapstruct mapper that uses a mapping like this:
#Mapping(target = "userId", source = "id.userId")
When I looked at the autogenerated mapstruct class I stubled upon that code:
if ( !foobar.hasId() ) {
return null;
}
This is a problem for me as hasId() does not what mapstruct expects here. Can I force mapstruct somehow to not generate code that uses this method but checks for id != null or something?
I could use a mapping like #Mapping(target = "userId", expression= "java(...)") but I think there should be another way.
Yes you can force MapStruct not to use those presenceCheckers. You can find more information in source presence checking in the documentation.
Basically the only way to do this is to provide an implementation of the MapStruct AccessorNamingStrategy. You can just extend the DefaultAccessorNamingStrategy and override itsisPresenceCheckMethod.
You have access to the method ExecutableElement and you can check the type of class it is in and other things as well.
MyAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
#Override
public boolean isPresenceCheckMethod(ExecutableElement element) {
//You can do your checks here. You can ignore certain methods, from certain classes
}
Remember to register your SPI with a file META-INF-/services/com.example.MyAccessorNamingStrategy
There is also the examples where you can find an example for the SPI.
To map a certain object with mapstruct I need some custom post processing which needs an additional parameter to do it's work:
#Mapper
public abstract class AlertConfigActionMapper {
#Mappings({ #Mapping(target = "label", ignore = true)})
public abstract AlertConfigActionTO map (AlertConfigAction action, Locale userLanguage);
#AfterMapping
public void setLabel (AlertConfigAction action, #MappingTarget AlertConfigActionTO to, Locale userLanguage) {
for (AlertConfigActionLabel label : action.getAlertConfigActionLabels()) {
if (label.getLanguage().equals(userLanguage)) {
to.setLabel(label.getLabel());
break;
} else if (label.getLanguage().equals(Locale.ENGLISH)) {
to.setLabel(label.getLabel());
}
}
}
}
This works just fine.
The problem starts when I add following method to this mapper:
public abstract ArrayList<AlertConfigActionTO> mapList (List<AlertConfigAction> actions, Locale userLanguage);
I need to pass this parameter (userLanguage) as well but mapstruct seems to 'break down' in this case: I generates following code for this part (which naturally gives a compilation error):
#Override
public List<AlertConfigActionTO> mapList(List<AlertConfigAction> actions, Locale userLanguage) {
if ( actions == null && userLanguage == null ) {
return null;
}
List<AlertConfigActionTO> list = new List<AlertConfigActionTO>();
return list;
}
I'm sure it is related to the parameter since if I remove it (from all mapping methods) then the mapList method is generated correctly.
What is needed to be done to allow custom parameters in this case?
What you describe is not possible (yet). Could you open a feature request in our issue tracker? We should provide means of denoting parameters as some sort of "context" which is passed down the call stack.
As a work-around for the time being, you might take a look at using a ThreadLocal which you set before invoking the mapping routine and which you access in your after-mapping customization. It's not elegant - and you need to make sure to clean up the thread local to avoid memory leaks - but it should do the trick.
I know that this question is quiet old, but I run into this issue, and starting at version 1.2 of mapstruct you can resolve it using #Context
So declaring the mapping for the list need to be like this :
public abstract ArrayList<AlertConfigActionTO> mapList (List<AlertConfigAction> actions, #Context Locale userLanguage);
Now, you juste need to add another non abstract mapping like this :
public AlertConfigActionTO mapConcrete (AlertConfigAction action, #Context Locale userLanguage){
return map (action, userLanguage);
}
I don't think it is possible. At least not that way. Problem is that you prepare interface/abstract class - and rest is done by the engine. And that engine expects methods with one parameter... There are decorators, but they go the same way. I would try to inject language. Create bean, mark it as session scoped, and find out. With Spring, you would use ScopedProxyMode for that... Not sure how that goes with CDI.
Other option is more workaround, then solution - maybe that AlertConfigAction can pass that information?