MapStruct Mapper as Spring Framework Converter - idiomatic use possible? - java

I'd like to combine MapStruct mappers with Spring's Conversion model. So I declare every Mapper interface as an extension of Spring's Converter:
#Mapper
public interface CarMapper extends Converter<Car, CarDto> {
#Override
CarDto convert(Car car);
}
I can then use the mapper beans by injecting the standard ConversionService:
class CarWarehouse {
#Autowired
private ConversionService conversionService;
...
public CarDto getCarInformation(Car car) {
return conversionService.convert(car, CarDto.class);
}
}
This works nicely, but I'm wondering whether there's a way to avoid injecting some Mappers into others directly via the uses attribute. What I'd like to do is tell a Mapper to use the ConversionService for employing another mapper. However, since the ConversionService's convert method doesn't match MapStruct's standard pattern for a mapping method, the code generation plugin doesn't recognise that it can use the service when looking for a submapping. Basically, what I want to do is write
#Mapper(uses=ConversionService.class)
public interface ParentMapper extends Converter<Parent, ParentDto>
instead of
#Mapper(uses={ChildMapper1.class, ChildMapper2.class, ChildMapper3.class})
public interface ParentMapper extends Converter<Parent, ParentDto>
Is there a way to achieve this?
Edit
Since it's been asked, let's say I've got a CarMapper defined as above, with the types Car and CarDto having an attribute wheel of type Wheel and WheelDto, respectively. Then I'd like to be able to define another Mapper like this:
#Mapper
public interface WheelMapper extends Converter<Wheel, WheelDto> {
#Override
WheelDto convert(Wheel wheel);
}
Right now, I'd have to add this Mapper explicitly:
#Mapper(uses = WheelMapper.class)
public interface CarMapper extends Converter<Car, CarDto>
Which would then give the generated CarMapperImpl an #Autowired member of type WheelMapper which would be called in order to map the attribute wheel.
However, what I'd like is that the generated code would look somewhat like this:
#Component
public class CarMapperImpl implements CarMapper {
#Autowired
private ConversionService conversionService;
#Override
public CarDto convert(Car car) {
CarDto carDto = new CarDto();
carDto.setWheel(conversionService.convert(car.getWheel(), WheelDto.class);
return carDto;
}
}

It's been more than a year since I asked this question, but now we've come up with an answer inside the MapStruct project itself - the MapStruct Spring Extensions project.
A CarMapper example is provided as an example within the project.

You can just skip passing a WheelMapper entirely, when you just have a CarMapper the generated CarMapperImpl will contain a logic to map Wheel <-> WheelDto as well. No need to pass anything to uses, making your issue obsolete.
carDto.setWheel( wheelToWheelDto( car.getWheel() ) );
with a method like;
protected WheelDto wheelToWheelDto(Wheel wheel) {
if ( wheel == null ) {
return null;
}
WheelDto wheelDto = new WheelDto();
wheelDto.setName( wheel.getName() );
return wheelDto;
}
I did try to achieve an intelligent injection of ConversionService through MapStruct, but it is not possible I think. You'd need support from MapStruct to achieve such a feat. It does not even consider injecting ConversionService. Maybe a custom generic mapper that is already implemented and uses ConversionService might work, but I was unable to do that! Though I don't see any reason for it since MapStruct is already creating all necessary smaller mappers from the parent mapper...

Frankly, I doubt you can achieve automatic wiring of ConversionService into generated mappers by MapStruct. The way that you described in the question (that wires individual mappers through uses annotation attribute), probably, the best that MapStruct can give out of the box.
However, there is workaround, if you absolutely need to use ConversionService to perform conversion for some DTOs (e.g. if you have some legacy converters, that you don't want to refactor to mappers). Basically, you can use combination of Mappers.getMapper static factory to get instance of ConversionService and default method in the mapper interface, to use ConversionService instance:
#Mapper(componentModel = "spring")
public interface CarMapper extends Converter<Car, CarDto> {
ConversionService CONVERSION_SERVICE = Mappers.getMapper(ConversionService.class);
#Override
default CarDto convert(Car car) {
if (car == null) {
return null;
}
CarDto carDto = new CarDto();
carDto.setEngine(CONVERSION_SERVICE.convert(car.getEngine(), EngineDto.class));
carDto.setWheel(CONVERSION_SERVICE.convert(car.getWheel(), WheelDto.class));
return carDto;
}
}
Note: as you can see, workaround requires to write CarMapper code. So, in my opinion, the solution with uses annotation attribute is cleaner approach. For example, you get almost the same result, by defining following interface:
#Mapper(componentModel = "spring",
uses = {EngineMapper.class, WheelMapper.class},
injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper extends Converter<Car, CarDto> {
#Override
CarDto convert(Car car);
Generated mapper:
#Component
public class CarMapperImpl implements CarMapper {
private final EngineMapper engineMapper;
private final WheelMapper wheelMapper;
#Autowired
public CarMapperImpl(EngineMapper engineMapper, WheelMapper wheelMapper) {
this.engineMapper = engineMapper;
this.wheelMapper = wheelMapper;
}
#Override
public CarDto convert(Car car) {
if (car == null) {
return null;
}
CarDto carDto = new CarDto();
carDto.setEngine(engineMapper.convert(car.getEngine()));
carDto.setWheel(wheelMapper.convert(car.getWheel()));
return carDto;
}
}

Related

Use a custom mapper inside another custom mapper with mapstruct (in default method)

I want to use MapperB inside MapperA's default method
Similar to this question:
How can I use another mapping from different class in mapstruct
However afaik this question did not ask for 'custom mappers', i.e. mappers that exist as their own interface.
How would I be able to do that?
I have an interface of MapperA and an interface of MapperB
How would I use the MapperB inside MapperA?
like so:
#Mapper
public interface MapperA {
#Autowired
MapperB mapperB;
default ArrayList<AudioDto> audiosToDto(List<Audio> audios, ApplicationUser loggedInUser) {
Stream<AudioDto> audiosStream = audios.stream().map((Audio audio) -> mapperB.audioToAudioDto(audio, loggedInUser));
return audiosStream.collect(Collectors.toCollection(ArrayList::new));
}
The above code didn't work. Now I tried adding #Component(to MapperA & MapperB) to be able to autowire it, but it's still giving me:
#Autowired <- Field injection is not recommended
MapperB mapperB; <- Variable 'audioMapper' might not have been initialized
even after maven-cleaning the project to get rid of the MapperAImpl.
You should define the MapperA as an abstract class instead of an interface, and use setter injection to inject MapperB as follows:
#Mapper(componentModel="spring")
public abstract class MapperA {
private MapperB mapperB;
#Autowired
public final void setMapperB(MapperB mapperB) {
this.mapperB = mapperB;
}
public ArrayList<AudioDto> audiosToDto(List<Audio> audios, ApplicationUser loggedInUser) {
Stream<AudioDto> audiosStream = audios.stream().map((Audio audio) -> mapperB.audioToAudioDto(audio, loggedInUser));
return audiosStream.collect(Collectors.toCollection(ArrayList::new));
}
}

MapStuct partial implementation - Java

My code goes like this,
#Mapper
public interface DtoMapper {
DtoMapper MAPPER = Mappers.getMapper(DtoMapper.class);
ExampleModel dtoToDdModel(ExampleDto exampleDto);
ExampleDto someOtherEntityToDto(OtherEntity otherEntity);
}
public class DtoMapperImpl implements DtoMapper {
#Override
public ExampleDto someOtherEntityToDto(OtherEntity otherEntity){
if ( OtherEntity == null ) {
return null;
}
// Conversion
}
// I don't want to define dtoToDdModel
}
Is it possible to only provide implementation of someOtherEntityToDto in DtoMapperImpl? While dtoToDdModel follows the default mapping provided by MapStruct?
Thank you in Advance!
Apply below annotation in DtoMapper interface
#InheritInverseConfiguration
ExampleModel dtoToDdModel(ExampleDto exampleDto)
MapStruct will only implement the abstract methods. Which means that if you provide a custom default methods then MapStruct will not implement it.
e.g.
#Mapper
public interface DtoMapper {
DtoMapper MAPPER = Mappers.getMapper(DtoMapper.class);
default ExampleModel dtoToDdModel(ExampleDto exampleDto) {
//TODO write implementation
}
ExampleDto someOtherEntityToDto(OtherEntity otherEntity);
}
Note: the #Mapper does not have to be an interface it can also be an abstract class.

Implements a Factory Pattern in java with using Generics

I need help with Java Generics.
My model is: I have some classes that extends a Dto (Data Transfer Object) and some classes that extends Entity (The model of my object to DB).
I have
interface Mapper<D extends Dto, E extends Entity>{
//Convert a Entity to Dto.
D toDto(E entity);
And I have some classes that implements this interface (i.e PersonMapper, BookMapper and so far and so on).
#Component
public class PersonMapper implements Mapper<PersonDto, PersonEntity> {
//implementation
}
#Component
public class BookMapper implements Mapper<BookDto, BookEntity> {
//implementation
}
What I want to do is to use Factory Pattern in order to select at runtime my Mapper, that depends from a String that I pass in input.
#Autowired
private PersonMapper personMapper;
#Autowired
private BookMapper bookMapper;
public <D extends Dto, E extends Entity> Mapper<D, E> selectMapper(String entity){
if ("Person".equalsIgnoreCase(entity))
return personMapper;
if("Book".equalsIgnoreCase(entity))
return bookMapper;
...
}
With this situation I have the following compile error:
Type mismatch: cannot convert from PersonMapper to Mapper<D,E>
My solutions:
1)
return (Mapper<D, E>) personMapper;
but I have a Warning:
Type Safety: `Unchecked class from personMapper to Mapper<D,H>`
2)
Using WildCard and castingb
public Mapper<Dto, Entity> selectMapper(String entity){
Mapper<? extends Dto, ? extends Entity> toReturn = null;
if ("Person".equalsIgnoreCase(entity))
toReturn = personMapper;
else if("Book".equalsIgnoreCase(entity))
toReturn = bookMapper;
...
return (Mapper<Dto, Entity>) toReturn;
}
But in this case but I have another time a Warning:
Type safety: Unchecked cast from Mapper<capture#29-of ? extends Dto,capture#30-of ? extends Entity> to Mapper<Dto,Entity>
It works but it doesn't seems to be a clean solution
3) Using wildcard as return type:
public Mapper<? extends Dto, ? extends HistoryEntity> selectMapper(String entity)
but you know, using wildcard as return type is not recommended at all and also doesn't help me because I would like to use this mapper and call mapper.toDto ensuring that the return type is an something that extends Dto.
====================================================================
I don't explain why If I write a class constructor like that
public Service<D extends Dto, E extends Entity>{
public Service(Mapper<D,E> mapper){
this.mapper = mapper;
}
}
and than I inject (for example) bookMapper it works.
If, instead, the Mapper<D,E> is in return type I cannot do such a kind of operation.
====================================================================
The help that I ask to you is:
how can I write a solution using clean code principles (avoiding compile warnings, sonarlint issue etc.) in order to implement this kind of logic?
Thank you very much, I appreciate a lot if you dedicate a little bit of your time helping me to solve my problem.
Those vars (D and E) about the caller and not about your code. The D and E are decided by the caller, so there is absolutely no way to guarantee that PersonDTO fits.
Make that Mapper<? extends DTO, ? extends Entity> (and no variables), and given that those are already the lower bounds, just Mapper<?, ?> - that'll work, you can write your return statements without any casts and without compiler errors or warnings.
Of course, it means the caller has a mostly useless type.
Generics are entirely 'compile time / write time' based. The JVM (java.exe) has no idea what generics are, and in fact most of them don't survive the compilation process. The one and only purpose of generics is to make the compiler flag incorrect code and avoid some casting, that is all.
The nature of turning that string into a Mapper is entirely runtime.
Ergo, if Mapper<?, ?> isn't sufficient, what you want isn't possible. You'd need to write compile/write-time checkable stuff, so the moment you use a String, it's impossible. For example, a method getPersonMapper() can of course return a Mapper<PersonDTO, PersonEntity>, no problem.
More generally (heh) it sounds like you're badly reinventing various wheels here. Look at tutorials of JDBI, JOOQ, and Hibernate to get some ideas about how java code is commonly written to interact with databases.
Factory Pattern is pattern that assemble or create something by factory methods, in you case what you need is just to get corresponding mapper by name, so there is a simple way to do that since the mapper beans are autowired, adding String getName() to Mapper interface then implements it for earch implementation, e.g. in BookMapper
#Override
public String getName() { return "Book"; }
use mapper name as key and mapper bean as value to store mapper beans in a map, then you can retrieve it by its name:
#Service
public class SimpleService {
private BookMapper bookMapper;
private PersonMapper personMapper;
private Map<String, Mapper<? extends DTO, ? extends Entity>> mappers = new HashMap<>();
public SimpleService(BookMapper bookMapper, PersonMapper personMapper) {
this.bookMapper = bookMapper;
this.personMapper = personMapper;
mappers.put(bookMapper.getName(), bookMapper);
mappers.put(personMapper.getName(), personMapper);
}
public Mapper<? extends DTO, ? extends Entity> getMapperByName(String mapperName) {
return mappers.get(mapperName);
}
}
and you can cast it to corresponding mapper without warning.
PersonMapper p = (PersonMapper) simpleService.getMapperByName("Person");
or you can put different mapper in their service and use the service to handle you biz likes codes below, after all, you need specified mappers to do specified operations:
if(personThings){
personService.doSomeThing();
}
if(bookThings){
bookService.doSomething();
}

How to correctly instantiate a class using #Autowired in a mapper interface in Spring Boot?

I am currently using mapstruct to map data between entities and DTOs, inside a mapper I need to instantiate a class using #Autowired, inside the class I need to instantiate I have a method that loads data into the cache, when I try to do the following: # Autowired RepositoryImpl repository; IntelliJ tells me: The variable 'repository' may not have been initialized. How could I use instantiate the class correctly or use the method I need?
mapper
#Service
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DataMapper {
**#Autowired
RepositoryImpl repository;**
}
default DetailTemp mapDetail(String itemType, counter){
**ItemType itemType = repository.getType(itemType);**
DetailTemp detailTemp = new DetailTemp();
detailTemp.setPosition(counter);
detailTemp.setItemType(itemType);
return DetailTemp;
}
}
According to this you need to be using an abstract class if you are using Spring components (i.e. #Autowired RepositoryImpl repository):
5.2. Inject Spring Components into the Mapper Sometimes, we'll need to utilize other Spring components inside our mapping logic. In this
case, we have to use an abstract class instead of an interface:
#Mapper(componentModel = "spring") public abstract class
SimpleDestinationMapperUsingInjectedService
Then, we can easily inject
the desired component using a well-known #Autowired annotation and use
it in our code:
#Mapper(componentModel = "spring") public abstract class
SimpleDestinationMapperUsingInjectedService {
#Autowired
protected SimpleService simpleService;
#Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")
public abstract SimpleDestination sourceToDestination(SimpleSource source); }
We must remember not to make the injected bean private!
This is because MapStruct has to access the object in the generated
implementation class.

Guice inject based on annotation value

I would like to use goolge/guice inject a value based on a class i provide with the annotation.
AutoConfig annotation
#BindingAnnotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.PARAMETER, ElementType.FIELD })
public #interface AutoConfig {
// default null not possible
Class<? extends Provider<? extends ConfigLoader<?>>> provider() default XMLAutoConfigProvider.class;
}
This is my annotation which allows configuring the type of config, that should be used for the annotated fields.
Usecase:
#AutoConfig()
ConfigLoader<?> defaultConfig;
#AutoConfig(provider = JsonConfigProvider)
ConfigLoader<?> jsonConfig;
I want to have two configs, one default/xml one and a json one. They will probably never occur in the same class at the same time. But i don't know when the one or the other is used. I used the approach with a class because they are provided by some dependencies/libs and this annotation will be used for some (plugable) submodules.
MyGuiceModule
public class MyGuiceModule extends AbstractModule {
#Override
protected void configure() {
bind(new TypeLiteral<ConfigLoader<?>>() {})
.annotatedWith(AutoConfig.class)
.toProvider(autoConfig.provider());
}
}
This the critical part, i just cannot imagine how to implement it.
So basically i just want to use the provider class specified in the annotation.
Its not necessary to use the provider class here too. Because autoConfig.provider().newInstance() is basically all i need. (I need to use a setter on the new instance but thats all i want to do at this place)
To sum it up all i really want to do is push the annotation (or its values to the provider) either using the get(AutoConfig autoConfig) or in the constructor.
Currently i only use the constructor to inject the configFile value i want to set on the newly generated config instance.
If you know that #AutoConfig(provider = JsonConfigProvider) ConfigLoader<?> jsonConfig is going to return you exactly the results of jsonConfigProvider.get(), and JsonConfigProvider obviously has a public parameterless constructor for newInstance to work, why wouldn't you just ask for a JsonConfigProvider in the first place?
Fundamentally Guice is just a Map<Key, Provider> with fancy wrapping. The bad news is that this makes variable bindings like "bind Foo<T> for all T" impossible to express concisely, and that includes your "bind #Annotation(T) Foo for all T". The good news is that you still have two options.
Bind each provider separately
Though you can't inspect annotations during provision (or tell Guice to do so for you), Guice will compare annotations using their equals methods if you bind an annotation instance rather than an annotation class (the way you would with Names.named("some-name")). This means that you can bind a ConfigLoader<?> with each expected annotation in a Module. Of course, this also means you'll have to have a list of possible ConfigLoader Providers available at configuration time, but they have to be compile-time constants anyway if you're using them as annotation parameters.
This solution works with constructor injection as well, but for fields you'll need both #Inject and #AutoConfig(...), and AutoConfig will need to keep its #BindingAnnotation meta-annotation.
To do this, you're going to have to write an implementation of your annotation, the way Guice does with NamedImpl. Note that the implementations of equals and hashCode must match the ones Java provides in java.lang.Annotation. Then it's just a matter of (redundantly) binding like this:
for(Class<ConfigLoader<?>> clazz : loaders) {
bind(ConfigLoader.class).annotatedWith(new AutoConfigImpl(clazz))
.toProvider(clazz);
}
The definition of equals is up to you, which means you can (and should) bind #AutoConfig(ConfigEnum.JSON) and keep the Guice bindings in your modules rather than specifying your requested implementation all over your codebase.
Use custom injections
You can also use custom injections to search your injected types for custom annotations like #AutoConfig. At this point, you'd be using Guice as a platform to interpret #AutoConfig instead of #Inject, which means that constructor injection won't work but that you can control your injection based on the injected instance, field name, field annotation, annotation parameters, or any combination thereof. If you choose this style, you can drop #BindingAnnotation from AutoConfig.
Use the example in the wiki article linked above as your template, but at minimum you'll need to:
Use bindListener on Binder or AbstractModule to match types that need this custom injection.
In the TypeListener you bind, search injected types for #AutoConfig-annotated fields, and if they have any matching methods then bind those matching methods to a MembersInjector or InjectionListener. You'll probably want to tease the class literal out of the annotation instance here, and pass in the Field and Class as constructor arguments to the MembersInjector/InjectionListener.
In the MembersInjector or InjectionListener you write, instantiate the provider and set the field to the instance the provider provides.
This is a very powerful feature, which would futher allow you to--for instance--automatically provide the configuration based on which instance you're injecting into or based on the name of the field. However, use it carefully and document it heavily, because it may be counter-intuitive to your coworkers that Guice is providing for an annotation other than #Inject. Also bear in mind that this won't work for constructor injection, so refactoring from field injection to constructor injection will cause Guice to complain that it's missing a required binding to instantiate the class.
I had a similar problem. I wanted to use a custom annotation that receives a enum param to choose the implementation. After a lot of research, debug and testing, I came to the following solution:
//enum to define authentication types
public enum AuthType {
Ldap, Saml
}
//custom annotation to be used in injection
#Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
#BindingAnnotation
public #interface Auth {
AuthType value();
}
//defintion of authenticator
public interface Authenticator {
public void doSomehting();
}
//Authenticator implementations
public class LdapAuthenticator implements Authenticator {
#Override
public void doSomehting() {
// doing ldap stuff
}
}
public class SamlAuthenticator implements Authenticator {
#Override
public void doSomehting() {
// doing saml stuff
}
}
public class MyModule extends AbstractModule {
// annotate fields to bind to implementations
private #Auth(AuthType.Ldap) Authenticator ldap;
private #Auth(AuthType.Saml) Authenticator saml;
#Override
protected void configure() {
//bind the implementation to the annotation from field
bindAnnotated("ldap", LdapAuthenticator.class);
bindAnnotated("saml", SamlAuthenticator.class);
}
private void bindAnnotated(String fieldName, Class<? extends Authenticator> implementation) {
try {
//get the annotation from fields, then bind it to implementation
Annotation ann = MyModule.class.getDeclaredField(fieldName).getAnnotation(Auth.class);
bind(Authenticator.class).annotatedWith(ann).to(implementation);
} catch (NoSuchFieldException | SecurityException e) {
throw new RuntimeException(e);
}
}
}
//usage: add #Auth(<AuthType>) to the dependency
public class ClientClass {
private Authenticator authenticator;
#Inject
public ClientClass(#Auth(AuthType.Ldap) Authenticator authenticator) {
this.authenticator = authenticator;
}
}
Check the documentation of Binder
I tested the Jeff Bowman solution, but it apparently works only binding to providers
As a BindingAnnotations#binding-annotations-with-attributes states equals() and hashCode() should be properly implemented. So given that there is MyAnnotation
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD, ElementType.PARAMETER})
public #interface MyAnnotation {
SomeEnum value() default SomeEnum.A;
}
which is used to specify SomeInterface implementation(SomeDefault and SomeOther), SomeModule class could look like
public class SomeModule extends AbstractModule {
#Override
protected void configure() {
bind(Key.get(SomeInterface.class, createAnnotationClass(A))).to(SomeDefault.class);
// more common binding expresion
bind(SomeInterface.class).annotatedWith(createAnnotationClass(B)).to(SomeDefault.class);
}
private Annotation createAnnotationClass(SomeEnum someEnum) {
return new MyAnnotation() {
#Override
public SomeEnum value() {
return someEnum;
}
#Override
public Class<? extends Annotation> annotationType() {
return MyAnnotation.class;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyAnnotationCl myAnnoCl = (MyAnnotationCl) o;
return A == myAnnoCl.getValue();
}
#Override
public int hashCode() {
// from java annotation documentation
return (127 * "value".hashCode()) ^ value().hashCode();
}
};
}
}
Then annotation could be used as follows:
public class DoSomethingWithSomething {
private final SomeInterface someImplementation;
#Inject
public DoSomethingWithSomething(
#MyAnnotation SomeInterface someDefault
// #MyAnnotation(A) SomeInterface someDefault
// #MyAnnotation(B) SomeInterface someOther
) {
this.someImplementation = someDefault;
}
}

Categories

Resources