Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface SpringMapperConfig {

String DEFAULT_CONVERSION_SERVICE_BEAN_NAME = "conversionService";
String DEFAULT_ADAPTER_CLASS_NAME = "ConversionServiceAdapter";
String DEFAULT_CONFIGURATION_CLASS_NAME = "ConverterRegistrationConfiguration";

/**
* The class name for the generated Configuration class,
* which is performing auto-registration of converters/mappers
* to Spring's {@link org.springframework.core.convert.ConversionService}.
*
* @return The class name for the generated Configuration.
*/
String converterRegistrationConfigurationClassName() default DEFAULT_CONFIGURATION_CLASS_NAME;

/**
* The package name for the generated Adapter between the MapStruct mappers and Spring's {@link
* org.springframework.core.convert.ConversionService}. If omitted or empty, the package name will
Expand All @@ -29,15 +43,15 @@
*
* @return The class name for the generated Adapter.
*/
String conversionServiceAdapterClassName() default "ConversionServiceAdapter";
String conversionServiceAdapterClassName() default DEFAULT_ADAPTER_CLASS_NAME;

/**
* The bean name for the Spring {@link org.springframework.core.convert.ConversionService} to use.
*
* @return The bean name for the Spring {@link
* org.springframework.core.convert.ConversionService}.
*/
String conversionServiceBeanName() default "";
String conversionServiceBeanName() default DEFAULT_CONVERSION_SERVICE_BEAN_NAME;

/**
* To set if the Lazy annotation will be added to the ConversionService's usage in the
Expand Down
64 changes: 64 additions & 0 deletions docs/src/docs/asciidoc/chapter-3-mapper-as-converter.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,70 @@ public class ConversionServiceAdapterIntegrationTest {
----
====

[[converterRegistrationConfigurationClassName]]
=== Modifying the name for the generated converter registration configuration class

By default, the converter registration configuration class will have name `ConverterRegistrationConfiguration`.
If you wish to change this, you can do so by setting the property `converterRegistrationConfigurationClassName`:

====
[source,java,linenums]
[subs="verbatim,attributes"]
----
@MapperConfig(componentModel = "spring")
@SpringMapperConfig(
converterRegistrationConfigurationClassName = "MyConfiguration",
generateConverterScan = true)
public interface MapstructConfig {}
----
====

This changes the generated class name to be the property's value:

====
[source,java,linenums]
[subs="verbatim,attributes"]
----
@Configuration
class MyConfiguration {
private final ConfigurableConversionService conversionService;

private final List<Converter<?, ?>> converters;

MyConfiguration(
@Qualifier("conversionService") final ConfigurableConversionService conversionService,
final List<Converter<?, ?>> converters) {
this.conversionService = conversionService;
this.converters = converters;
}

@PostConstruct
void registerConverters() {
converters.forEach(conversionService::addConverter);
}
}
----
====

Also this changes reference to converter registration configuration class from generated ConverterScan class:

====
[source,java,linenums]
[subs="verbatim,attributes"]
----
@ComponentScan
@Target(TYPE)
@Import(MyConfiguration.class)
@Documented
@Retention(RUNTIME)
@Repeatable(ConverterScans.class)
public @interface ConverterScan {
...
}
----
====


[[adapterMethodName]]
=== Modifying the name for the generated adapter method

Expand Down
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 libs.assertj
testImplementation libs.bundles.junit.jupiter
implementation libs.jsr250
implementation libs.mapstruct.core
annotationProcessor libs.mapstruct.processor
implementation libs.spring.context
implementation libs.spring.core
testImplementation libs.spring.test
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.mapstruct.extensions.spring.example.customconfiguration;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.core.convert.converter.Converter;
import org.mapstruct.extensions.spring.example.Car;
import org.mapstruct.extensions.spring.example.CarDto;

@Mapper(config = MapperSpringConfig.class)
public interface CarMapper extends Converter<Car, CarDto> {
@Mapping(target = "seats", source = "seatConfiguration")
CarDto convert(Car car);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.mapstruct.extensions.spring.example.customconfiguration;

import org.mapstruct.MapperConfig;
import org.mapstruct.extensions.spring.SpringMapperConfig;

@MapperConfig(componentModel = "spring", uses = ConversionServiceAdapter.class)
@SpringMapperConfig(
converterRegistrationConfigurationClassName = "MyConfiguration",
generateConverterScan = true)
public interface MapperSpringConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.mapstruct.extensions.spring.example.customconfiguration;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.mapstruct.extensions.spring.example.customconfiguration;

import org.mapstruct.Mapper;
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)
public interface WheelMapper extends Converter<Wheel, WheelDto> {
@Override
WheelDto convert(Wheel source);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.mapstruct.extensions.spring.example.customconfiguration;

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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.mapstruct.extensions.spring.example.customconfiguration;

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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package org.mapstruct.extensions.spring.example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mapstruct.extensions.spring.example.customconfiguration.ConversionServiceAdapter;
import org.mapstruct.extensions.spring.example.customconfiguration.ConverterScan;
import org.mapstruct.extensions.spring.example.customconfiguration.MapperSpringConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.ArrayList;
import java.util.List;

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;

@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
@ComponentScan("org.mapstruct.extensions.spring.example.customconfiguration")
static class ScanConfiguration {
@Bean
public ConversionService conversionService() {
return new DefaultConversionService();
}
}

@Test
void shouldKnowAllMappers() {
then(conversionService.canConvert(Car.class, CarDto.class)).isTrue();
then(conversionService.canConvert(SeatConfiguration.class, SeatConfigurationDto.class)).isTrue();
then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue();
then(conversionService.canConvert(Wheels.class, List.class)).isTrue();
then(conversionService.canConvert(List.class, Wheels.class)).isTrue();
then(conversionService.canConvert(
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(WheelDto.class)),
TypeDescriptor.valueOf((Wheels.class))))
.isTrue();
}

@Test
void shouldMapAllAttributes() {
// Given
final Car car = new Car();
car.setMake(TEST_MAKE);
car.setType(TEST_CAR_TYPE);
final SeatConfiguration seatConfiguration = new SeatConfiguration();
seatConfiguration.setSeatMaterial(TEST_SEAT_MATERIAL);
seatConfiguration.setNumberOfSeats(TEST_NUMBER_OF_SEATS);
car.setSeatConfiguration(seatConfiguration);
final Wheels wheels = new Wheels();
final ArrayList<Wheel> wheelsList = new ArrayList<>();
final Wheel wheel = new Wheel();
wheel.setDiameter(TEST_DIAMETER);
wheel.setPosition(TEST_WHEEL_POSITION);
wheelsList.add(wheel);
wheels.setWheelsList(wheelsList);
car.setWheels(wheels);

// When
final CarDto mappedCar = conversionService.convert(car, CarDto.class);

// Then
then(mappedCar).isNotNull();
then(mappedCar.getMake()).isEqualTo(TEST_MAKE);
then(mappedCar.getType()).isEqualTo(String.valueOf(TEST_CAR_TYPE));
final SeatConfigurationDto mappedCarSeats = mappedCar.getSeats();
then(mappedCarSeats).isNotNull();
then(mappedCarSeats.getSeatCount()).isEqualTo(TEST_NUMBER_OF_SEATS);
then(mappedCarSeats.getMaterial()).isEqualTo(String.valueOf(TEST_SEAT_MATERIAL));
final WheelDto expectedWheelDto = new WheelDto();
expectedWheelDto.setPosition(String.valueOf(TEST_WHEEL_POSITION));
expectedWheelDto.setDiameter(TEST_DIAMETER);
then(mappedCar.getWheels()).hasSize(1).containsExactly(expectedWheelDto);
}

@Test
void shouldMapGenericSourceType() {
// Given
final WheelDto dto = new WheelDto();
dto.setPosition(String.valueOf(TEST_WHEEL_POSITION));
dto.setDiameter(TEST_DIAMETER);
final List<WheelDto> dtoList = new ArrayList<>();
dtoList.add(dto);

// When
final Wheels convertedWheels = conversionService.convert(dtoList, Wheels.class);

// Then
final Wheel expectedWheel = new Wheel();
expectedWheel.setPosition(TEST_WHEEL_POSITION);
expectedWheel.setDiameter(TEST_DIAMETER);
then(convertedWheels).isNotNull().hasSize(1).containsExactly(expectedWheel);
}
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package org.mapstruct.extensions.spring.converter;

import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.mapstruct.extensions.spring.SpringMapperConfig.DEFAULT_CONFIGURATION_CLASS_NAME;
import static org.mapstruct.extensions.spring.SpringMapperConfig.DEFAULT_CONVERSION_SERVICE_BEAN_NAME;

import com.squareup.javapoet.ClassName;
import java.util.List;

public class ConversionServiceAdapterDescriptor {
public static final String DEFAULT_CONVERTER_SCAN_CLASS_NAME = "ConverterScan";
public static final String DEFAULT_CONVERTER_SCANS_CLASS_NAME = "ConverterScans";

private ClassName adapterClassName;
private String conversionServiceBeanName;
private String conversionServiceBeanName = DEFAULT_CONVERSION_SERVICE_BEAN_NAME;
private List<FromToMapping> fromToMappings;
private boolean lazyAnnotatedConversionServiceBean;
private String configurationClassName = DEFAULT_CONFIGURATION_CLASS_NAME;

private boolean generateConverterScan;

boolean hasNonDefaultConversionServiceBeanName() {
return isNotEmpty(getConversionServiceBeanName());
return isNotEmpty(getConversionServiceBeanName())
&& !DEFAULT_CONVERSION_SERVICE_BEAN_NAME.equals(getConversionServiceBeanName());
}

public ClassName getAdapterClassName() {
Expand Down Expand Up @@ -56,6 +63,15 @@ public ConversionServiceAdapterDescriptor lazyAnnotatedConversionServiceBean(
return this;
}

public ConversionServiceAdapterDescriptor configurationClassName(
final String configurationClassName) {
this.configurationClassName = configurationClassName;
return this;
}

public String getConfigurationClassName() {
return configurationClassName;
}

public boolean isGenerateConverterScan() {
return generateConverterScan;
Expand All @@ -67,14 +83,14 @@ public ConversionServiceAdapterDescriptor generateConverterScan(final boolean ge
}

public ClassName getConverterScanClassName() {
return ClassName.get(getAdapterClassName().packageName(), "ConverterScan");
return ClassName.get(getAdapterClassName().packageName(), DEFAULT_CONVERTER_SCAN_CLASS_NAME);
}

public ClassName getConverterScansClassName() {
return ClassName.get(getAdapterClassName().packageName(), "ConverterScans");
return ClassName.get(getAdapterClassName().packageName(), DEFAULT_CONVERTER_SCANS_CLASS_NAME);
}

public ClassName getConverterRegistrationConfigurationClassName() {
return ClassName.get(getAdapterClassName().packageName(), "ConverterRegistrationConfiguration");
return ClassName.get(getAdapterClassName().packageName(), configurationClassName);
}
}
Loading