#3229: Implement InjectionStrategy.SETTER

This commit is contained in:
Lucas Resch 2023-07-30 10:38:24 +02:00 committed by GitHub
parent 230e84efd1
commit 0460c373c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 813 additions and 16 deletions

View File

@ -10,6 +10,7 @@ package org.mapstruct;
* JSR330 / Jakarta.
*
* @author Kevin Grüneberg
* @author Lucas Resch
*/
public enum InjectionStrategy {
@ -17,5 +18,8 @@ public enum InjectionStrategy {
FIELD,
/** Annotations are written on the constructor **/
CONSTRUCTOR
CONSTRUCTOR,
/** A dedicated setter method is created */
SETTER
}

View File

@ -102,7 +102,7 @@ A mapper which uses other mapper classes (see <<invoking-other-mappers>>) will o
[[injection-strategy]]
=== Injection strategy
When using <<using-dependency-injection,dependency injection>>, you can choose between field and constructor injection.
When using <<using-dependency-injection,dependency injection>>, you can choose between constructor, field, or setter injection.
This can be done by either providing the injection strategy via `@Mapper` or `@MapperConfig` annotation.
.Using constructor injection
@ -120,9 +120,13 @@ public interface CarMapper {
The generated mapper will inject classes defined in the **uses** attribute if MapStruct has detected that it needs to use an instance of it for a mapping.
When `InjectionStrategy#CONSTRUCTOR` is used, the constructor will have the appropriate annotation and the fields won't.
When `InjectionStrategy#FIELD` is used, the annotation is on the field itself.
When `InjectionStrategy#SETTER` is used the annotation is on a generated setter method.
For now, the default injection strategy is field injection, but it can be configured with <<configuration-options>>.
It is recommended to use constructor injection to simplify testing.
When you define mappers in Spring with circular dependencies compilation may fail.
In that case utilize the `InjectionStrategy#SETTER` strategy.
[TIP]
====
For abstract classes or decorators setter injection should be used.

View File

@ -13,5 +13,6 @@ package org.mapstruct.ap.internal.gem;
public enum InjectionStrategyGem {
FIELD,
CONSTRUCTOR;
CONSTRUCTOR,
SETTER;
}

View File

@ -0,0 +1,59 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.model;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.Type;
/**
* @author Lucas Resch
*/
public class AnnotatedSetter extends GeneratedTypeMethod {
private final Field field;
private final Collection<Annotation> methodAnnotations;
private final Collection<Annotation> parameterAnnotations;
public AnnotatedSetter(Field field, Collection<Annotation> methodAnnotations,
Collection<Annotation> parameterAnnotations) {
this.field = field;
this.methodAnnotations = methodAnnotations;
this.parameterAnnotations = parameterAnnotations;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> importTypes = new HashSet<>( field.getImportTypes() );
for ( Annotation annotation : methodAnnotations ) {
importTypes.addAll( annotation.getImportTypes() );
}
for ( Annotation annotation : parameterAnnotations ) {
importTypes.addAll( annotation.getImportTypes() );
}
return importTypes;
}
public Type getType() {
return field.getType();
}
public String getFieldName() {
return field.getVariableName();
}
public Collection<Annotation> getMethodAnnotations() {
return methodAnnotations;
}
public Collection<Annotation> getParameterAnnotations() {
return parameterAnnotations;
}
}

View File

@ -82,7 +82,7 @@ public abstract class GeneratedType extends ModelElement {
private final Type mapperDefinitionType;
private final List<Annotation> annotations;
private final List<MappingMethod> methods;
private final List<GeneratedTypeMethod> methods;
private final SortedSet<Type> extraImportedTypes;
private final boolean suppressGeneratorTimestamp;
@ -110,7 +110,7 @@ public abstract class GeneratedType extends ModelElement {
this.extraImportedTypes = extraImportedTypes;
this.annotations = new ArrayList<>();
this.methods = methods;
this.methods = new ArrayList<>(methods);
this.fields = fields;
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
@ -161,7 +161,7 @@ public abstract class GeneratedType extends ModelElement {
annotations.add( annotation );
}
public List<MappingMethod> getMethods() {
public List<GeneratedTypeMethod> getMethods() {
return methods;
}
@ -204,7 +204,7 @@ public abstract class GeneratedType extends ModelElement {
addIfImportRequired( importedTypes, mapperDefinitionType );
for ( MappingMethod mappingMethod : methods ) {
for ( GeneratedTypeMethod mappingMethod : methods ) {
for ( Type type : mappingMethod.getImportTypes() ) {
addIfImportRequired( importedTypes, type );
}

View File

@ -0,0 +1,15 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.model;
import org.mapstruct.ap.internal.model.common.ModelElement;
/**
* @author Filip Hrisafov
*/
public abstract class GeneratedTypeMethod extends ModelElement {
}

View File

@ -14,7 +14,6 @@ import java.util.Objects;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.Accessibility;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method;
@ -27,7 +26,7 @@ import static org.mapstruct.ap.internal.util.Strings.join;
*
* @author Gunnar Morling
*/
public abstract class MappingMethod extends ModelElement {
public abstract class MappingMethod extends GeneratedTypeMethod {
private final String name;
private final List<Parameter> parameters;

View File

@ -11,10 +11,11 @@ import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.model.AnnotatedConstructor;
import org.mapstruct.ap.internal.model.AnnotatedSetter;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.AnnotationMapperReference;
import org.mapstruct.ap.internal.model.Decorator;
@ -77,6 +78,9 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
if ( injectionStrategy == InjectionStrategyGem.CONSTRUCTOR ) {
buildConstructors( mapper );
}
else if ( injectionStrategy == InjectionStrategyGem.SETTER ) {
buildSetters( mapper );
}
return mapper;
}
@ -110,6 +114,42 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
return mapperReferences;
}
private void buildSetters(Mapper mapper) {
List<MapperReference> mapperReferences = toMapperReferences( mapper.getFields() );
for ( MapperReference mapperReference : mapperReferences ) {
if ( mapperReference.isUsed() ) {
AnnotatedSetter setter = new AnnotatedSetter(
mapperReference,
getMapperReferenceAnnotations(),
Collections.emptyList()
);
mapper.getMethods().add( setter );
}
}
Decorator decorator = mapper.getDecorator();
if ( decorator != null ) {
List<Annotation> mapperReferenceAnnotations = getMapperReferenceAnnotations();
Set<Type> mapperReferenceAnnotationsTypes = mapperReferenceAnnotations
.stream()
.map( Annotation::getType )
.collect( Collectors.toSet() );
for ( Field field : decorator.getFields() ) {
if ( field instanceof AnnotationMapperReference ) {
List<Annotation> fieldAnnotations = ( (AnnotationMapperReference) field ).getAnnotations();
List<Annotation> qualifiers = extractMissingAnnotations(
fieldAnnotations,
mapperReferenceAnnotationsTypes
);
decorator.getMethods().add( new AnnotatedSetter( field, mapperReferenceAnnotations, qualifiers ) );
}
}
}
}
private void buildConstructors(Mapper mapper) {
if ( !toMapperReferences( mapper.getFields() ).isEmpty() ) {
AnnotatedConstructor annotatedConstructor = buildAnnotatedConstructorForMapper( mapper );
@ -197,17 +237,33 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
AnnotationMapperReference annotationMapperReference = mapperReferenceIterator.next();
mapperReferenceIterator.remove();
List<Annotation> qualifiers = new ArrayList<>();
for ( Annotation annotation : annotationMapperReference.getAnnotations() ) {
if ( !mapperReferenceAnnotationsTypes.contains( annotation.getType() ) ) {
qualifiers.add( annotation );
}
}
List<Annotation> qualifiers = extractMissingAnnotations(
annotationMapperReference.getAnnotations(),
mapperReferenceAnnotationsTypes
);
mapperReferenceIterator.add( annotationMapperReference.withNewAnnotations( qualifiers ) );
}
}
/**
* Extract all annotations from {@code annotations} that do not have a type in {@code annotationTypes}.
*
* @param annotations the annotations from which we need to extract information
* @param annotationTypes the annotation types to ignore
* @return the annotations that are not in the {@code annotationTypes}
*/
private List<Annotation> extractMissingAnnotations(List<Annotation> annotations,
Set<Type> annotationTypes) {
List<Annotation> qualifiers = new ArrayList<>();
for ( Annotation annotation : annotations ) {
if ( !annotationTypes.contains( annotation.getType() ) ) {
qualifiers.add( annotation );
}
}
return qualifiers;
}
protected boolean additionalPublicEmptyConstructor() {
return false;
}

View File

@ -0,0 +1,14 @@
<#--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.AnnotatedSetter" -->
<#list methodAnnotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
public void set${fieldName?cap_first}(<#list parameterAnnotations as annotation><#nt><@includeModel object=annotation/> </#list><@includeModel object=type/> ${fieldName}) {
this.${fieldName} = ${fieldName};
}

View File

@ -0,0 +1,26 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.decorator.spring.setter;
import org.mapstruct.DecoratedWith;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.SETTER)
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
@Mapping( target = "name", ignore = true )
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}

View File

@ -0,0 +1,29 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.decorator.spring.setter;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public abstract class PersonMapperDecorator implements PersonMapper {
private PersonMapper decoratorDelegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = decoratorDelegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
@Autowired
public void setDecoratorDelegate(@Qualifier("delegate") PersonMapper decoratorDelegate) {
this.decoratorDelegate = decoratorDelegate;
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.decorator.spring.setter;
import java.util.Calendar;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.mapstruct.ap.test.decorator.Address;
import org.mapstruct.ap.test.decorator.AddressDto;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithSpring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@WithClasses({
Person.class,
PersonDto.class,
Address.class,
AddressDto.class,
PersonMapper.class,
PersonMapperDecorator.class
})
@WithSpring
@IssueKey("3229")
@ComponentScan(basePackageClasses = SpringDecoratorTest.class)
@Configuration
public class SpringDecoratorTest {
@Autowired
private PersonMapper personMapper;
private ConfigurableApplicationContext context;
@BeforeEach
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@AfterEach
public void springDown() {
if ( context != null ) {
context.close();
}
}
@ProcessorTest
public void shouldInvokeDecoratorMethods() {
//given
Calendar birthday = Calendar.getInstance();
birthday.set( 1928, Calendar.MAY, 23 );
Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) );
//when
PersonDto personDto = personMapper.personToPersonDto( person );
//then
assertThat( personDto ).isNotNull();
assertThat( personDto.getName() ).isEqualTo( "Gary Crant" );
assertThat( personDto.getAddress() ).isNotNull();
assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" );
}
@ProcessorTest
public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() {
//given
Address address = new Address( "42 Ocean View Drive" );
//when
AddressDto addressDto = personMapper.addressToAddressDto( address );
//then
assertThat( addressDto ).isNotNull();
assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" );
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jakarta.setter;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;
/**
* @author Filip Hrisafov
*/
@Mapper( componentModel = MappingConstants.ComponentModel.JAKARTA,
uses = GenderJakartaSetterMapper.class,
injectionStrategy = InjectionStrategy.SETTER )
public interface CustomerJakartaSetterMapper {
@Mapping(target = "gender", source = "gender")
CustomerDto asTarget(CustomerEntity customerEntity);
}

View File

@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jakarta.setter;
import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;
/**
* @author Filip Hrisafov
*/
@Mapper(config = SetterJakartaConfig.class)
public interface GenderJakartaSetterMapper {
@ValueMappings({
@ValueMapping(source = "MALE", target = "M"),
@ValueMapping(source = "FEMALE", target = "F")
})
GenderDto mapToDto(Gender gender);
}

View File

@ -0,0 +1,57 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jakarta.setter;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithJakartaInject;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import org.springframework.context.annotation.Configuration;
import static java.lang.System.lineSeparator;
/**
* @author Filip Hrisafov
*/
@WithClasses({
CustomerDto.class,
CustomerEntity.class,
Gender.class,
GenderDto.class,
CustomerJakartaSetterMapper.class,
GenderJakartaSetterMapper.class,
SetterJakartaConfig.class
})
@IssueKey("3229")
@Configuration
@WithJakartaInject
public class JakartaSetterMapperTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@ProcessorTest
public void shouldHaveSetterInjection() {
String method = "@Inject" + lineSeparator() +
" public void setGenderJakartaSetterMapper(GenderJakartaSetterMapper genderJakartaSetterMapper) {" +
lineSeparator() + " this.genderJakartaSetterMapper = genderJakartaSetterMapper;" +
lineSeparator() + " }";
generatedSource.forMapper( CustomerJakartaSetterMapper.class )
.content()
.contains( "import jakarta.inject.Inject;" )
.contains( "import jakarta.inject.Named;" )
.contains( "import jakarta.inject.Singleton;" )
.contains( "private GenderJakartaSetterMapper genderJakartaSetterMapper;" )
.doesNotContain( "@Inject" + lineSeparator() + " private GenderJakartaSetterMapper" )
.contains( method );
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jakarta.setter;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.MapperConfig;
import org.mapstruct.MappingConstants;
/**
* @author Filip Hrisafov
*/
@MapperConfig(componentModel = MappingConstants.ComponentModel.JAKARTA,
injectionStrategy = InjectionStrategy.SETTER)
public interface SetterJakartaConfig {
}

View File

@ -0,0 +1,26 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jsr330.setter;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;
/**
* @author Filip Hrisafov
*/
@Mapper( componentModel = MappingConstants.ComponentModel.JSR330,
uses = GenderJsr330SetterMapper.class,
injectionStrategy = InjectionStrategy.SETTER )
public interface CustomerJsr330SetterMapper {
@Mapping(target = "gender", source = "gender")
CustomerDto asTarget(CustomerEntity customerEntity);
}

View File

@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jsr330.setter;
import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;
/**
* @author Filip Hrisafov
*/
@Mapper(config = SetterJsr330Config.class)
public interface GenderJsr330SetterMapper {
@ValueMappings({
@ValueMapping(source = "MALE", target = "M"),
@ValueMapping(source = "FEMALE", target = "F")
})
GenderDto mapToDto(Gender gender);
}

View File

@ -0,0 +1,98 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jsr330.setter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithJavaxInject;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import static java.lang.System.lineSeparator;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@WithClasses({
CustomerDto.class,
CustomerEntity.class,
Gender.class,
GenderDto.class,
CustomerJsr330SetterMapper.class,
GenderJsr330SetterMapper.class,
SetterJsr330Config.class
})
@IssueKey("3229")
@ComponentScan(basePackageClasses = CustomerJsr330SetterMapper.class)
@Configuration
@WithJavaxInject
public class Jsr330SetterMapperTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@Autowired
private CustomerJsr330SetterMapper customerMapper;
private ConfigurableApplicationContext context;
@BeforeEach
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@AfterEach
public void springDown() {
if ( context != null ) {
context.close();
}
}
@ProcessorTest
public void shouldConvertToTarget() {
// given
CustomerEntity customerEntity = new CustomerEntity();
customerEntity.setName( "Samuel" );
customerEntity.setGender( Gender.MALE );
// when
CustomerDto customerDto = customerMapper.asTarget( customerEntity );
// then
assertThat( customerDto ).isNotNull();
assertThat( customerDto.getName() ).isEqualTo( "Samuel" );
assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M );
}
@ProcessorTest
public void shouldHaveSetterInjection() {
String method = "@Inject" + lineSeparator() +
" public void setGenderJsr330SetterMapper(GenderJsr330SetterMapper genderJsr330SetterMapper) {" +
lineSeparator() + " this.genderJsr330SetterMapper = genderJsr330SetterMapper;" +
lineSeparator() + " }";
generatedSource.forMapper( CustomerJsr330SetterMapper.class )
.content()
.contains( "import javax.inject.Inject;" )
.contains( "import javax.inject.Named;" )
.contains( "import javax.inject.Singleton;" )
.contains( "private GenderJsr330SetterMapper genderJsr330SetterMapper;" )
.doesNotContain( "@Inject" + lineSeparator() + " private GenderJsr330SetterMapper" )
.contains( method );
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.jsr330.setter;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.MapperConfig;
import org.mapstruct.MappingConstants;
/**
* @author Filip Hrisafov
*/
@MapperConfig(componentModel = MappingConstants.ComponentModel.JSR330,
injectionStrategy = InjectionStrategy.SETTER)
public interface SetterJsr330Config {
}

View File

@ -0,0 +1,23 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.spring.setter;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity;
/**
* @author Lucas Resch
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,
uses = { CustomerSpringSetterMapper.class, GenderSpringSetterMapper.class },
injectionStrategy = InjectionStrategy.SETTER)
public interface CustomerRecordSpringSetterMapper {
CustomerRecordDto asTarget(CustomerRecordEntity customerRecordEntity);
}

View File

@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.spring.setter;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;
/**
* @author Lucas Resch
*/
@Mapper( componentModel = MappingConstants.ComponentModel.SPRING,
uses = GenderSpringSetterMapper.class,
injectionStrategy = InjectionStrategy.SETTER )
public interface CustomerSpringSetterMapper {
@Mapping(target = "gender", source = "gender")
CustomerDto asTarget(CustomerEntity customerEntity);
}

View File

@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.spring.setter;
import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;
/**
* @author Lucas Resch
*/
@Mapper(config = SetterSpringConfig.class)
public interface GenderSpringSetterMapper {
@ValueMappings({
@ValueMapping(source = "MALE", target = "M"),
@ValueMapping(source = "FEMALE", target = "F")
})
GenderDto mapToDto(Gender gender);
}

View File

@ -0,0 +1,17 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.spring.setter;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.MapperConfig;
import org.mapstruct.MappingConstants;
/**
* @author Lucas Resch
*/
@MapperConfig(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.SETTER)
public interface SetterSpringConfig {
}

View File

@ -0,0 +1,119 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.injectionstrategy.spring.setter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.DefaultTimeZone;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto;
import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity;
import org.mapstruct.ap.test.injectionstrategy.shared.Gender;
import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithSpring;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import static java.lang.System.lineSeparator;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test setter injection for component model spring.
*
* @author Lucas Resch
*/
@WithClasses( {
CustomerRecordDto.class,
CustomerRecordEntity.class,
CustomerDto.class,
CustomerEntity.class,
Gender.class,
GenderDto.class,
CustomerRecordSpringSetterMapper.class,
CustomerSpringSetterMapper.class,
GenderSpringSetterMapper.class,
SetterSpringConfig.class
} )
@IssueKey( "3229" )
@ComponentScan(basePackageClasses = CustomerSpringSetterMapper.class)
@Configuration
@WithSpring
@DefaultTimeZone("Europe/Berlin")
public class SpringSetterMapperTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@Autowired
private CustomerRecordSpringSetterMapper customerRecordMapper;
private ConfigurableApplicationContext context;
@BeforeEach
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@AfterEach
public void springDown() {
if ( context != null ) {
context.close();
}
}
@ProcessorTest
public void shouldConvertToTarget() throws Exception {
// given
CustomerEntity customerEntity = new CustomerEntity();
customerEntity.setName( "Samuel" );
customerEntity.setGender( Gender.MALE );
CustomerRecordEntity customerRecordEntity = new CustomerRecordEntity();
customerRecordEntity.setCustomer( customerEntity );
customerRecordEntity.setRegistrationDate( createDate( "31-08-1982 10:20:56" ) );
// when
CustomerRecordDto customerRecordDto = customerRecordMapper.asTarget( customerRecordEntity );
// then
assertThat( customerRecordDto ).isNotNull();
assertThat( customerRecordDto.getCustomer() ).isNotNull();
assertThat( customerRecordDto.getCustomer().getName() ).isEqualTo( "Samuel" );
assertThat( customerRecordDto.getCustomer().getGender() ).isEqualTo( GenderDto.M );
assertThat( customerRecordDto.getRegistrationDate() ).isNotNull();
assertThat( customerRecordDto.getRegistrationDate() ).hasToString( "1982-08-31T10:20:56.000+02:00" );
}
@ProcessorTest
public void shouldHaveSetterInjection() {
String method = "@Autowired" + lineSeparator() +
" public void setGenderSpringSetterMapper(GenderSpringSetterMapper genderSpringSetterMapper) {" +
lineSeparator() + " this.genderSpringSetterMapper = genderSpringSetterMapper;" +
lineSeparator() + " }";
generatedSource.forMapper( CustomerSpringSetterMapper.class )
.content()
.contains( "private GenderSpringSetterMapper genderSpringSetterMapper;" )
.contains( method );
}
private Date createDate(String date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" );
return sdf.parse( date );
}
}