-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
88 generate additional delegating mappers for inheritinverseconfigura…
…tion (#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
- Loading branch information
Showing
37 changed files
with
1,211 additions
and
325 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
annotations/src/main/java/org/mapstruct/extensions/spring/DelegatingConverter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.mapstruct.extensions.spring; | ||
|
||
import static java.lang.annotation.ElementType.METHOD; | ||
import static java.lang.annotation.RetentionPolicy.SOURCE; | ||
|
||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Indicates that the system should generate a delegating {@link | ||
* org.springframework.core.convert.converter.Converter} that will call the annotated method in its | ||
* own {@link org.springframework.core.convert.converter.Converter#convert(Object)}. | ||
*/ | ||
@Target(METHOD) | ||
@Retention(SOURCE) | ||
public @interface DelegatingConverter {} |
62 changes: 62 additions & 0 deletions
62
docs/src/docs/asciidoc/chapter-6-delegating-converters.asciidoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
[[delegatingConverters]] | ||
== Delegating Converters | ||
|
||
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. | ||
Since this is not how Spring sees `Converter` s, this inverted mapping method will be "invisible" to the `ConversionService`. | ||
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`. | ||
This class will have a method counterpart in the generated Adapter just like the ones annotated with `@Mapper`. | ||
|
||
Take this `Converter` for example: | ||
|
||
==== | ||
[source,java,linenums] | ||
[subs="verbatim,attributes"] | ||
---- | ||
@Mapper(config = MapperSpringConfig.class) | ||
public interface CarMapper extends Converter<Car, CarDto> { | ||
@Mapping(target = "seats", source = "seatConfiguration") | ||
CarDto convert(Car car); | ||
@InheritInverseConfiguration | ||
@DelegatingConverter | ||
Car invertConvert(CarDto carDto); | ||
} | ||
---- | ||
==== | ||
|
||
Notice the combination of `@InheritInverseConfiguration` and `@DelegatingConverter` on the `invertConvert` method. | ||
The `@DelegatingConverter` will lead to a class like this: | ||
|
||
==== | ||
[source,java,linenums] | ||
[subs="verbatim,attributes"] | ||
---- | ||
@Component | ||
public class CarDtoToCarConverter implements Converter<CarDto, Car> { | ||
private CarMapper delegateMapper; | ||
public CarDtoToCarConverter(@Autowired final CarMapper delegateMapper) { | ||
this.delegateMapper = delegateMapper; | ||
} | ||
@Override | ||
public Car convert(final CarDto source) { | ||
return delegateMapper.invertConvert(source); | ||
} | ||
} | ||
---- | ||
==== | ||
|
||
The generated Adapter will contain a method counterpart like this: | ||
|
||
==== | ||
[source,java,linenums] | ||
[subs="verbatim,attributes"] | ||
---- | ||
public Car mapCarDtoToCar(final CarDto source) { | ||
return (Car) conversionService.convert(source, TypeDescriptor.valueOf(CarDto.class), TypeDescriptor.valueOf(Car.class)); | ||
} | ||
---- | ||
==== | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
dependencies { | ||
annotationProcessor project(":extensions") | ||
implementation projects.examples.model | ||
implementation projects.annotations | ||
|
||
testImplementation projects.testExtensions | ||
testImplementation libs.assertj | ||
testImplementation libs.bundles.junit.jupiter | ||
implementation libs.mapstruct.core | ||
annotationProcessor libs.mapstruct.processor | ||
implementation libs.spring.context | ||
implementation libs.spring.core | ||
testImplementation libs.spring.test | ||
} |
19 changes: 19 additions & 0 deletions
19
.../src/main/java/org/mapstruct/extensions/spring/example/delegatingconverter/CarMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package org.mapstruct.extensions.spring.example.delegatingconverter; | ||
|
||
import org.mapstruct.InheritInverseConfiguration; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.Mapping; | ||
import org.mapstruct.extensions.spring.DelegatingConverter; | ||
import org.mapstruct.extensions.spring.example.Car; | ||
import org.mapstruct.extensions.spring.example.CarDto; | ||
import org.springframework.core.convert.converter.Converter; | ||
|
||
@Mapper(config = MapperSpringConfig.class) | ||
public interface CarMapper extends Converter<Car, CarDto> { | ||
@Mapping(target = "seats", source = "seatConfiguration") | ||
CarDto convert(Car car); | ||
|
||
@InheritInverseConfiguration | ||
@DelegatingConverter | ||
Car invertConvert(CarDto carDto); | ||
} |
8 changes: 8 additions & 0 deletions
8
.../java/org/mapstruct/extensions/spring/example/delegatingconverter/MapperSpringConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.mapstruct.extensions.spring.example.delegatingconverter; | ||
|
||
import org.mapstruct.MapperConfig; | ||
import org.mapstruct.extensions.spring.SpringMapperConfig; | ||
|
||
@MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class) | ||
@SpringMapperConfig | ||
public interface MapperSpringConfig {} |
20 changes: 20 additions & 0 deletions
20
.../org/mapstruct/extensions/spring/example/delegatingconverter/SeatConfigurationMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.mapstruct.extensions.spring.example.delegatingconverter; | ||
|
||
import org.mapstruct.InheritInverseConfiguration; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.Mapping; | ||
import org.mapstruct.extensions.spring.DelegatingConverter; | ||
import org.mapstruct.extensions.spring.example.SeatConfiguration; | ||
import org.mapstruct.extensions.spring.example.SeatConfigurationDto; | ||
import org.springframework.core.convert.converter.Converter; | ||
|
||
@Mapper(config = MapperSpringConfig.class) | ||
public interface SeatConfigurationMapper extends Converter<SeatConfiguration, SeatConfigurationDto> { | ||
@Mapping(target = "seatCount", source = "numberOfSeats") | ||
@Mapping(target = "material", source = "seatMaterial") | ||
SeatConfigurationDto convert(SeatConfiguration seatConfiguration); | ||
|
||
@InheritInverseConfiguration | ||
@DelegatingConverter | ||
SeatConfiguration invertConvert(SeatConfigurationDto seatConfigurationDto); | ||
} |
20 changes: 20 additions & 0 deletions
20
...rc/main/java/org/mapstruct/extensions/spring/example/delegatingconverter/WheelMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package org.mapstruct.extensions.spring.example.delegatingconverter; | ||
|
||
import org.mapstruct.InheritInverseConfiguration; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.extensions.spring.AdapterMethodName; | ||
import org.mapstruct.extensions.spring.DelegatingConverter; | ||
import org.mapstruct.extensions.spring.example.Wheel; | ||
import org.mapstruct.extensions.spring.example.WheelDto; | ||
import org.springframework.core.convert.converter.Converter; | ||
|
||
@Mapper(config = MapperSpringConfig.class) | ||
@AdapterMethodName("toDto") | ||
public interface WheelMapper extends Converter<Wheel, WheelDto> { | ||
@Override | ||
WheelDto convert(Wheel source); | ||
|
||
@InheritInverseConfiguration | ||
@DelegatingConverter | ||
Wheel invertConvert(WheelDto wheelDto); | ||
} |
13 changes: 13 additions & 0 deletions
13
...java/org/mapstruct/extensions/spring/example/delegatingconverter/WheelsDtoListMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package org.mapstruct.extensions.spring.example.delegatingconverter; | ||
|
||
import java.util.List; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.extensions.spring.example.WheelDto; | ||
import org.mapstruct.extensions.spring.example.Wheels; | ||
import org.springframework.core.convert.converter.Converter; | ||
|
||
@Mapper(config = MapperSpringConfig.class) | ||
public interface WheelsDtoListMapper extends Converter<List<WheelDto>, Wheels> { | ||
@Override | ||
Wheels convert(List<WheelDto> source); | ||
} |
14 changes: 14 additions & 0 deletions
14
...c/main/java/org/mapstruct/extensions/spring/example/delegatingconverter/WheelsMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package org.mapstruct.extensions.spring.example.delegatingconverter; | ||
|
||
import java.util.List; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.extensions.spring.example.Wheel; | ||
import org.mapstruct.extensions.spring.example.WheelDto; | ||
import org.mapstruct.extensions.spring.example.Wheels; | ||
import org.springframework.core.convert.converter.Converter; | ||
|
||
@Mapper(config = MapperSpringConfig.class, imports = Wheel.class) | ||
public interface WheelsMapper extends Converter<Wheels, List<WheelDto>> { | ||
@Override | ||
List<WheelDto> convert(Wheels source); | ||
} |
74 changes: 74 additions & 0 deletions
74
...xtensions/spring/example/delegatingconverter/ConversionServiceAdapterIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package org.mapstruct.extensions.spring.example.delegatingconverter; | ||
|
||
import static org.assertj.core.api.BDDAssertions.then; | ||
import static org.mapstruct.extensions.spring.example.CarType.OTHER; | ||
import static org.mapstruct.extensions.spring.example.SeatMaterial.LEATHER; | ||
import static org.mapstruct.extensions.spring.example.WheelPosition.RIGHT_FRONT; | ||
|
||
import java.util.ArrayList; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mapstruct.extensions.spring.example.*; | ||
import org.mapstruct.extensions.spring.test.ConverterScan; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.core.convert.ConversionService; | ||
import org.springframework.test.context.junit.jupiter.SpringExtension; | ||
|
||
@ExtendWith(SpringExtension.class) | ||
public class ConversionServiceAdapterIntegrationTest { | ||
private static final String TEST_MAKE = "Volvo"; | ||
private static final CarType TEST_CAR_TYPE = OTHER; | ||
private static final int TEST_NUMBER_OF_SEATS = 5; | ||
private static final SeatMaterial TEST_SEAT_MATERIAL = LEATHER; | ||
private static final int TEST_DIAMETER = 20; | ||
private static final WheelPosition TEST_WHEEL_POSITION = RIGHT_FRONT; | ||
|
||
@Autowired private ConversionService conversionService; | ||
|
||
@Configuration | ||
@ConverterScan(basePackageClasses = MapperSpringConfig.class) | ||
static class ScanConfiguration{} | ||
|
||
@Test | ||
void shouldKnowDelegatingMappers() { | ||
then(conversionService.canConvert(CarDto.class, Car.class)).isTrue(); | ||
then(conversionService.canConvert(SeatConfigurationDto.class, SeatConfiguration.class)) | ||
.isTrue(); | ||
then(conversionService.canConvert(WheelDto.class, Wheel.class)).isTrue(); | ||
} | ||
|
||
@Test | ||
void shouldMapAllAttributes() { | ||
// Given | ||
final var carDto = new CarDto(); | ||
carDto.setMake(TEST_MAKE); | ||
carDto.setType(TEST_CAR_TYPE.name()); | ||
final var seatConfigurationDto = new SeatConfigurationDto(); | ||
seatConfigurationDto.setMaterial(TEST_SEAT_MATERIAL.name()); | ||
seatConfigurationDto.setSeatCount(TEST_NUMBER_OF_SEATS); | ||
carDto.setSeats(seatConfigurationDto); | ||
final var wheelsList = new ArrayList<WheelDto>(); | ||
final var wheel = new WheelDto(); | ||
wheel.setDiameter(TEST_DIAMETER); | ||
wheel.setPosition(TEST_WHEEL_POSITION.name()); | ||
wheelsList.add(wheel); | ||
carDto.setWheels(wheelsList); | ||
|
||
// When | ||
final var mappedCar = conversionService.convert(carDto, Car.class); | ||
|
||
// Then | ||
then(mappedCar).isNotNull(); | ||
then(mappedCar.getMake()).isEqualTo(TEST_MAKE); | ||
then(mappedCar.getType()).isEqualTo(TEST_CAR_TYPE); | ||
final var mappedCarSeatConfiguration = mappedCar.getSeatConfiguration(); | ||
then(mappedCarSeatConfiguration).isNotNull(); | ||
then(mappedCarSeatConfiguration.getNumberOfSeats()).isEqualTo(TEST_NUMBER_OF_SEATS); | ||
then(mappedCarSeatConfiguration.getSeatMaterial()).isEqualTo(TEST_SEAT_MATERIAL); | ||
final var expectedWheel = new Wheel(); | ||
expectedWheel.setPosition(TEST_WHEEL_POSITION); | ||
expectedWheel.setDiameter(TEST_DIAMETER); | ||
then(mappedCar.getWheels()).hasSize(1).containsExactly(expectedWheel); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
...ions/src/main/java/org/mapstruct/extensions/spring/converter/AdapterRelatedGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package org.mapstruct.extensions.spring.converter; | ||
|
||
import com.squareup.javapoet.TypeSpec; | ||
|
||
import java.io.Writer; | ||
import java.time.Clock; | ||
|
||
public abstract class AdapterRelatedGenerator extends Generator { | ||
protected AdapterRelatedGenerator(Clock clock) { | ||
super(clock); | ||
} | ||
|
||
public final void writeGeneratedCodeToOutput( | ||
final ConversionServiceAdapterDescriptor descriptor, final Writer out) { | ||
writeGeneratedCodeToOutput( | ||
() -> descriptor.getAdapterClassName().packageName(), | ||
() -> createMainTypeSpec(descriptor), | ||
out); | ||
} | ||
|
||
protected abstract TypeSpec createMainTypeSpec(ConversionServiceAdapterDescriptor descriptor); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.