Skip to content

Commit 0de9825

Browse files
authored
88 generate additional delegating mappers for inheritinverseconfiguration (#94)
* WIP: Working quite well, but for some reason the example now generates two separate converter adapters in different packages. * WIP: Unified into single Processor; however, we are still getting a separate adapter class for the second round. We need some type of marker mechanism in order to prevent generating a second (meaningless) adapter in the second round. * WIP: Seems to work now by using Filer.createResource instead of Filer.createSourceFile. Still requires polishing, integration tests and documentation. * WIP: Switched to generating full class instead of abstract class, because the implementation generation was suppressed. Generated converters are still in the adapter. Requires polishing, integration tests and documentation. * WIP: Resource generation meant that there were no class files, but this now works as long as the generated clas is NOT annotated as a @Mapper so the adapter generation does not get retriggered. Requires polishing, integration tests and documentation. * Documentation completed * Self-review: No need to change mapstruct core to implementation scope. * Self-review: No new processor after all
1 parent 80ac8a5 commit 0de9825

File tree

37 files changed

+1211
-325
lines changed

37 files changed

+1211
-325
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.mapstruct.extensions.spring;
2+
3+
import static java.lang.annotation.ElementType.METHOD;
4+
import static java.lang.annotation.RetentionPolicy.SOURCE;
5+
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.Target;
8+
9+
/**
10+
* Indicates that the system should generate a delegating {@link
11+
* org.springframework.core.convert.converter.Converter} that will call the annotated method in its
12+
* own {@link org.springframework.core.convert.converter.Converter#convert(Object)}.
13+
*/
14+
@Target(METHOD)
15+
@Retention(SOURCE)
16+
public @interface DelegatingConverter {}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
[[delegatingConverters]]
2+
== Delegating Converters
3+
4+
Applying MapStruct's https://mapstruct.org/documentation/stable/reference/html/#inverse-mappings[inverse mappings] requires a second mapping method inside a `@Mapper`-annotated interface or abstract class.
5+
Since this is not how Spring sees `Converter` s, this inverted mapping method will be "invisible" to the `ConversionService`.
6+
Adding the annotation `@DelegatingConverter` to the same method will lead to the generation of a separate class which implements `Converter` and does nothing more than call the annotated method in its own `convert`.
7+
This class will have a method counterpart in the generated Adapter just like the ones annotated with `@Mapper`.
8+
9+
Take this `Converter` for example:
10+
11+
====
12+
[source,java,linenums]
13+
[subs="verbatim,attributes"]
14+
----
15+
@Mapper(config = MapperSpringConfig.class)
16+
public interface CarMapper extends Converter<Car, CarDto> {
17+
@Mapping(target = "seats", source = "seatConfiguration")
18+
CarDto convert(Car car);
19+
20+
@InheritInverseConfiguration
21+
@DelegatingConverter
22+
Car invertConvert(CarDto carDto);
23+
}
24+
----
25+
====
26+
27+
Notice the combination of `@InheritInverseConfiguration` and `@DelegatingConverter` on the `invertConvert` method.
28+
The `@DelegatingConverter` will lead to a class like this:
29+
30+
====
31+
[source,java,linenums]
32+
[subs="verbatim,attributes"]
33+
----
34+
@Component
35+
public class CarDtoToCarConverter implements Converter<CarDto, Car> {
36+
private CarMapper delegateMapper;
37+
38+
public CarDtoToCarConverter(@Autowired final CarMapper delegateMapper) {
39+
this.delegateMapper = delegateMapper;
40+
}
41+
42+
@Override
43+
public Car convert(final CarDto source) {
44+
return delegateMapper.invertConvert(source);
45+
}
46+
}
47+
----
48+
====
49+
50+
The generated Adapter will contain a method counterpart like this:
51+
52+
====
53+
[source,java,linenums]
54+
[subs="verbatim,attributes"]
55+
----
56+
public Car mapCarDtoToCar(final CarDto source) {
57+
return (Car) conversionService.convert(source, TypeDescriptor.valueOf(CarDto.class), TypeDescriptor.valueOf(Car.class));
58+
}
59+
----
60+
====
61+
62+
Please note: The behaviour of `@DelegatingConverter` is _not_ tied to `@InheritInverseConfiguration`; however, this is the only use case we are aware of where it provides meaningful value.

docs/src/docs/asciidoc/mapstruct-spring-extensions-reference-guide.asciidoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,6 @@ include::chapter-3-mapper-as-converter.asciidoc[]
2626

2727
include::chapter-4-external-conversions.asciidoc[]
2828

29-
include::chapter-5-test-extensions.asciidoc[]
29+
include::chapter-5-test-extensions.asciidoc[]
30+
31+
include::chapter-6-delegating-converters.asciidoc[]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
dependencies {
2+
annotationProcessor project(":extensions")
3+
implementation projects.examples.model
4+
implementation projects.annotations
5+
6+
testImplementation projects.testExtensions
7+
testImplementation libs.assertj
8+
testImplementation libs.bundles.junit.jupiter
9+
implementation libs.mapstruct.core
10+
annotationProcessor libs.mapstruct.processor
11+
implementation libs.spring.context
12+
implementation libs.spring.core
13+
testImplementation libs.spring.test
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.mapstruct.extensions.spring.example.delegatingconverter;
2+
3+
import org.mapstruct.InheritInverseConfiguration;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.Mapping;
6+
import org.mapstruct.extensions.spring.DelegatingConverter;
7+
import org.mapstruct.extensions.spring.example.Car;
8+
import org.mapstruct.extensions.spring.example.CarDto;
9+
import org.springframework.core.convert.converter.Converter;
10+
11+
@Mapper(config = MapperSpringConfig.class)
12+
public interface CarMapper extends Converter<Car, CarDto> {
13+
@Mapping(target = "seats", source = "seatConfiguration")
14+
CarDto convert(Car car);
15+
16+
@InheritInverseConfiguration
17+
@DelegatingConverter
18+
Car invertConvert(CarDto carDto);
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.mapstruct.extensions.spring.example.delegatingconverter;
2+
3+
import org.mapstruct.MapperConfig;
4+
import org.mapstruct.extensions.spring.SpringMapperConfig;
5+
6+
@MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class)
7+
@SpringMapperConfig
8+
public interface MapperSpringConfig {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.mapstruct.extensions.spring.example.delegatingconverter;
2+
3+
import org.mapstruct.InheritInverseConfiguration;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.Mapping;
6+
import org.mapstruct.extensions.spring.DelegatingConverter;
7+
import org.mapstruct.extensions.spring.example.SeatConfiguration;
8+
import org.mapstruct.extensions.spring.example.SeatConfigurationDto;
9+
import org.springframework.core.convert.converter.Converter;
10+
11+
@Mapper(config = MapperSpringConfig.class)
12+
public interface SeatConfigurationMapper extends Converter<SeatConfiguration, SeatConfigurationDto> {
13+
@Mapping(target = "seatCount", source = "numberOfSeats")
14+
@Mapping(target = "material", source = "seatMaterial")
15+
SeatConfigurationDto convert(SeatConfiguration seatConfiguration);
16+
17+
@InheritInverseConfiguration
18+
@DelegatingConverter
19+
SeatConfiguration invertConvert(SeatConfigurationDto seatConfigurationDto);
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.mapstruct.extensions.spring.example.delegatingconverter;
2+
3+
import org.mapstruct.InheritInverseConfiguration;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.extensions.spring.AdapterMethodName;
6+
import org.mapstruct.extensions.spring.DelegatingConverter;
7+
import org.mapstruct.extensions.spring.example.Wheel;
8+
import org.mapstruct.extensions.spring.example.WheelDto;
9+
import org.springframework.core.convert.converter.Converter;
10+
11+
@Mapper(config = MapperSpringConfig.class)
12+
@AdapterMethodName("toDto")
13+
public interface WheelMapper extends Converter<Wheel, WheelDto> {
14+
@Override
15+
WheelDto convert(Wheel source);
16+
17+
@InheritInverseConfiguration
18+
@DelegatingConverter
19+
Wheel invertConvert(WheelDto wheelDto);
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.mapstruct.extensions.spring.example.delegatingconverter;
2+
3+
import java.util.List;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.extensions.spring.example.WheelDto;
6+
import org.mapstruct.extensions.spring.example.Wheels;
7+
import org.springframework.core.convert.converter.Converter;
8+
9+
@Mapper(config = MapperSpringConfig.class)
10+
public interface WheelsDtoListMapper extends Converter<List<WheelDto>, Wheels> {
11+
@Override
12+
Wheels convert(List<WheelDto> source);
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.mapstruct.extensions.spring.example.delegatingconverter;
2+
3+
import java.util.List;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.extensions.spring.example.Wheel;
6+
import org.mapstruct.extensions.spring.example.WheelDto;
7+
import org.mapstruct.extensions.spring.example.Wheels;
8+
import org.springframework.core.convert.converter.Converter;
9+
10+
@Mapper(config = MapperSpringConfig.class, imports = Wheel.class)
11+
public interface WheelsMapper extends Converter<Wheels, List<WheelDto>> {
12+
@Override
13+
List<WheelDto> convert(Wheels source);
14+
}

0 commit comments

Comments
 (0)