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.
Related
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);
}
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)
Can I do it with reflection or something like that?
I have been searching for a while and there seems to be different approaches, here is a summary:
reflections library is pretty popular if u don't mind adding the dependency. It would look like this:
Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
ServiceLoader (as per erickson answer) and it would look like this:
ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
for (Pet implClass : loader) {
System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
}
Note that for this to work you need to define Petas a ServiceProviderInterface (SPI) and declare its implementations. you do that by creating a file in resources/META-INF/services with the name examples.reflections.Pet and declare all implementations of Pet in it
examples.reflections.Dog
examples.reflections.Cat
package-level annotation. here is an example:
Package[] packages = Package.getPackages();
for (Package p : packages) {
MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
if (annotation != null) {
Class<?>[] implementations = annotation.implementationsOfPet();
for (Class<?> impl : implementations) {
System.out.println(impl.getSimpleName());
}
}
}
and the annotation definition:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PACKAGE)
public #interface MyPackageAnnotation {
Class<?>[] implementationsOfPet() default {};
}
and you must declare the package-level annotation in a file named package-info.java inside that package. here are sample contents:
#MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
package examples.reflections;
Note that only packages that are known to the ClassLoader at that time will be loaded by a call to Package.getPackages().
In addition, there are other approaches based on URLClassLoader that will always be limited to classes that have been already loaded, Unless you do a directory-based search.
What erickson said, but if you still want to do it then take a look at Reflections. From their page:
Using Reflections you can query your metadata for:
get all subtypes of some type
get all types annotated with some annotation
get all types annotated with some annotation, including annotation parameters matching
get all methods annotated with some
In general, it's expensive to do this. To use reflection, the class has to be loaded. If you want to load every class available on the classpath, that will take time and memory, and isn't recommended.
If you want to avoid this, you'd need to implement your own class file parser that operated more efficiently, instead of reflection. A byte code engineering library may help with this approach.
The Service Provider mechanism is the conventional means to enumerate implementations of a pluggable service, and has become more established with the introduction of Project Jigsaw (modules) in Java 9. Use the ServiceLoader in Java 6, or implement your own in earlier versions. I provided an example in another answer.
Spring has a pretty simple way to acheive this:
public interface ITask {
void doStuff();
}
#Component
public class MyTask implements ITask {
public void doStuff(){}
}
Then you can autowire a list of type ITask and Spring will populate it with all implementations:
#Service
public class TaskService {
#Autowired
private List<ITask> tasks;
}
The most robust mechanism for listing all classes that implement a given interface is currently ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author.)
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
.enableClassInfo().scan()) {
for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
foundImplementingClass(ci); // Do something with the ClassInfo object
}
}
With ClassGraph it's pretty simple:
Groovy code to find implementations of my.package.MyInterface:
#Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}
What erikson said is best. Here's a related question and answer thread - http://www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html
The Apache BCEL library allows you to read classes without loading them. I believe it will be faster because you should be able to skip the verification step. The other problem with loading all classes using the classloader is that you will suffer a huge memory impact as well as inadvertently run any static code blocks which you probably do not want to do.
The Apache BCEL library link - http://jakarta.apache.org/bcel/
Yes, the first step is to identify "all" the classes that you cared about. If you already have this information, you can enumerate through each of them and use instanceof to validate the relationship. A related article is here: https://web.archive.org/web/20100226233915/www.javaworld.com/javaworld/javatips/jw-javatip113.html
Also, if you are writing an IDE plugin (where what you are trying to do is relatively common), then the IDE typically offers you more efficient ways to access the class hierarchy of the current state of the user code.
I ran into the same issue. My solution was to use reflection to examine all of the methods in an ObjectFactory class, eliminating those that were not createXXX() methods returning an instance of one of my bound POJOs. Each class so discovered is added to a Class[] array, which was then passed to the JAXBContext instantiation call. This performs well, needing only to load the ObjectFactory class, which was about to be needed anyway. I only need to maintain the ObjectFactory class, a task either performed by hand (in my case, because I started with POJOs and used schemagen), or can be generated as needed by xjc. Either way, it is performant, simple, and effective.
A new version of #kaybee99's answer, but now returning what the user asks: the implementations...
Spring has a pretty simple way to acheive this:
public interface ITask {
void doStuff();
default ITask getImplementation() {
return this;
}
}
#Component
public class MyTask implements ITask {
public void doStuff(){}
}
Then you can autowire a list of type ITask and Spring will populate it with all implementations:
#Service
public class TaskService {
#Autowired(required = false)
private List<ITask> tasks;
if ( tasks != null)
for (ITask<?> taskImpl: tasks) {
taskImpl.doStuff();
}
}
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?
I'm a newbie with MapStruct, and need some help with that.
I have a Source class, with an attribute
Object input;
Which, in runtime, returns a custom object named TicketDetails.
Now, in my target class there is a attribute named,
MyTicket myTicket;
which, I need to map with an attribute of TicketDetails object.
For, better understanding, I'm writing the normal java code example below.
SourceClassModel sourceClassModel = new SourceClassModel();
TargetClassModel targetClassModel = new TargetClassModel();
//mapping
TicketDetails ticketDetails = (TicketDetails) sourceClassModel.getInput();
targetClassModel.setMyTicket(ticketDetails.getMyTicket);
Now, my question is, how to achieve this case using MapStruct?
Either on a used mapper (see #Mapper#uses()) or in a non-abstract method on the mapper itself - in case it is an abstract class and not an interface - define the mapping from Object to TicketDetails yourself:
TicketDetails asTicketDetails(Object details) {
return (TicketDetails) details;
}
The generated method for the conversion of SourceClassModel to TargetClassModel will then invoke that manually written method for converting the myTicket property.