#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. * JSR330 / Jakarta.
* *
* @author Kevin Grüneberg * @author Kevin Grüneberg
* @author Lucas Resch
*/ */
public enum InjectionStrategy { public enum InjectionStrategy {
@ -17,5 +18,8 @@ public enum InjectionStrategy {
FIELD, FIELD,
/** Annotations are written on the constructor **/ /** 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]]
=== 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. This can be done by either providing the injection strategy via `@Mapper` or `@MapperConfig` annotation.
.Using constructor injection .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. 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#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#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>>. 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. 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] [TIP]
==== ====
For abstract classes or decorators setter injection should be used. 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 { public enum InjectionStrategyGem {
FIELD, 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 Type mapperDefinitionType;
private final List<Annotation> annotations; private final List<Annotation> annotations;
private final List<MappingMethod> methods; private final List<GeneratedTypeMethod> methods;
private final SortedSet<Type> extraImportedTypes; private final SortedSet<Type> extraImportedTypes;
private final boolean suppressGeneratorTimestamp; private final boolean suppressGeneratorTimestamp;
@ -110,7 +110,7 @@ public abstract class GeneratedType extends ModelElement {
this.extraImportedTypes = extraImportedTypes; this.extraImportedTypes = extraImportedTypes;
this.annotations = new ArrayList<>(); this.annotations = new ArrayList<>();
this.methods = methods; this.methods = new ArrayList<>(methods);
this.fields = fields; this.fields = fields;
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
@ -161,7 +161,7 @@ public abstract class GeneratedType extends ModelElement {
annotations.add( annotation ); annotations.add( annotation );
} }
public List<MappingMethod> getMethods() { public List<GeneratedTypeMethod> getMethods() {
return methods; return methods;
} }
@ -204,7 +204,7 @@ public abstract class GeneratedType extends ModelElement {
addIfImportRequired( importedTypes, mapperDefinitionType ); addIfImportRequired( importedTypes, mapperDefinitionType );
for ( MappingMethod mappingMethod : methods ) { for ( GeneratedTypeMethod mappingMethod : methods ) {
for ( Type type : mappingMethod.getImportTypes() ) { for ( Type type : mappingMethod.getImportTypes() ) {
addIfImportRequired( importedTypes, type ); 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 java.util.Set;
import org.mapstruct.ap.internal.model.common.Accessibility; 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.Parameter;
import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.Method;
@ -27,7 +26,7 @@ import static org.mapstruct.ap.internal.util.Strings.join;
* *
* @author Gunnar Morling * @author Gunnar Morling
*/ */
public abstract class MappingMethod extends ModelElement { public abstract class MappingMethod extends GeneratedTypeMethod {
private final String name; private final String name;
private final List<Parameter> parameters; private final List<Parameter> parameters;

View File

@ -11,10 +11,11 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.model.AnnotatedConstructor; 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.Annotation;
import org.mapstruct.ap.internal.model.AnnotationMapperReference; import org.mapstruct.ap.internal.model.AnnotationMapperReference;
import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Decorator;
@ -77,6 +78,9 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
if ( injectionStrategy == InjectionStrategyGem.CONSTRUCTOR ) { if ( injectionStrategy == InjectionStrategyGem.CONSTRUCTOR ) {
buildConstructors( mapper ); buildConstructors( mapper );
} }
else if ( injectionStrategy == InjectionStrategyGem.SETTER ) {
buildSetters( mapper );
}
return mapper; return mapper;
} }
@ -110,6 +114,42 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
return mapperReferences; 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) { private void buildConstructors(Mapper mapper) {
if ( !toMapperReferences( mapper.getFields() ).isEmpty() ) { if ( !toMapperReferences( mapper.getFields() ).isEmpty() ) {
AnnotatedConstructor annotatedConstructor = buildAnnotatedConstructorForMapper( mapper ); AnnotatedConstructor annotatedConstructor = buildAnnotatedConstructorForMapper( mapper );
@ -197,17 +237,33 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
AnnotationMapperReference annotationMapperReference = mapperReferenceIterator.next(); AnnotationMapperReference annotationMapperReference = mapperReferenceIterator.next();
mapperReferenceIterator.remove(); mapperReferenceIterator.remove();
List<Annotation> qualifiers = new ArrayList<>(); List<Annotation> qualifiers = extractMissingAnnotations(
for ( Annotation annotation : annotationMapperReference.getAnnotations() ) { annotationMapperReference.getAnnotations(),
if ( !mapperReferenceAnnotationsTypes.contains( annotation.getType() ) ) { mapperReferenceAnnotationsTypes
qualifiers.add( annotation ); );
}
}
mapperReferenceIterator.add( annotationMapperReference.withNewAnnotations( qualifiers ) ); 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() { protected boolean additionalPublicEmptyConstructor() {
return false; 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 );
}
}