#592 Fix Decorator-support for component model 'spring' and 'jsr330', extend decorator integration-tests, add examples to @DecoratedWith javadoc

This commit is contained in:
Andreas Gudian 2015-07-07 11:35:14 +02:00
parent 4eaacbcfe6
commit 590363cf2f
28 changed files with 959 additions and 60 deletions

View File

@ -29,14 +29,131 @@ import java.lang.annotation.Target;
* <p>
* A typical decorator implementation will be an abstract class and only implement/override a subset of the methods of
* the mapper type which it decorates. All methods not implemented or overridden by the decorator will be implemented by
* the code generator.
* the code generator by delegating to the generated mapper implementation.
* <p>
* <b>NOTE:</b> The usage of decorated mappers differs depending on the selected component model.
* <p>
* <b>NOTE:</b> This annotation is not supported for the component model {@code cdi}. Use CDI's own
* <a href="https://docs.jboss.org/cdi/spec/1.0/html/decorators.html">{@code @Decorator}</a> feature instead.
* <p>
* <b>NOTE:</b> The decorator feature when used with component model {@code jsr330} is considered <em>experimental</em>
* and it may change in future releases.
* <p>
* <h2>Examples</h2>
* <p>
* For the examples below, consider the following mapper declaration:
*
* <pre>
* &#64;Mapper(componentModel = "...")
* &#64;DecoratedWith(PersonMapperDecorator.class)
* public interface PersonMapper {
*
* &#64;Mapping(target = "name", ignore = true)
* PersonDto personToPersonDto(Person person);
*
* AddressDto addressToAddressDto(Address address); // not touched by the decorator
* }
* </pre>
*
* <h3>1. Component model 'default'</h3>
* <h4>Referencing the original mapper in the decorator</h4>
* <p>
* If a constructor with a single parameter accepting the type of the decorated mapper is present, a delegate with
* generated implementations of all the mapper methods will be passed to this constructor. A typical implementation will
* store the passed delegate in a field of the decorator and make use of it in the decorator methods.
* store the passed delegate in a field of the decorator and make use of it in the decorator methods:
*
* <pre>
* public abstract class PersonMapperDecorator implements PersonMapper {
*
* private PersonMapper delegate;
*
* public PersonMapperDecorator(PersonMapper delegate) {
* this.delegate = delegate;
* }
*
* &#64;Override
* public PersonDto personToPersonDto(Person person) {
* PersonDto dto = delegate.personToPersonDto( person );
* dto.setName( person.getFirstName() + " " + person.getLastName() );
*
* return dto;
* }
* }
* </pre>
*
* <h4>Using the decorated mapper</h4>
* <p>
* Nothing special needs to be done. When using {@code Mappers.getMapper( PersonMapper.class )}, the <em>decorator</em>
* is returned, with the injected original mapper.
* <h3>2. Component model 'spring'</h3>
* <h4>Referencing the original mapper in the decorator</h4>
* <p>
* The generated implementation of the original mapper is annotated with the Spring's {@code @Qualifier("delegate")}. To
* autowire that bean in your decorator, add that qualifier annotation as well:
*
* <pre>
* public abstract class PersonMapperDecorator implements PersonMapper {
*
* &#64;Autowired
* &#64;Qualifier("delegate")
* private PersonMapper delegate;
*
* &#64;Override
* public PersonDto personToPersonDto(Person person) {
* PersonDto dto = delegate.personToPersonDto( person );
* dto.setName( person.getFirstName() + " " + person.getLastName() );
*
* return dto;
* }
* }
* </pre>
*
* <h4>Using the decorated mapper in the decorator</h4>
* <p>
* The generated class that extends the decorator is annotated with Spring's {@code @Primary} annotation. To autowire
* the decorated mapper in the application, nothing special needs to be done:
*
* <pre>
* &#64;Autowired
* private PersonMapper personMapper; // injects the decorator, with the injected original mapper
* </pre>
*
* <h3>3. Component model 'jsr330'</h3>
* <h4>Referencing the original mapper</h4>
* <p>
* JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated
* implementation of the original mapper is annotated with the {@code @Named("fully-qualified-name-of-generated-impl")}.
* To inject that bean in your decorator, add the same annotation to the delegate field:
*
* <pre>
* public abstract class PersonMapperDecorator implements PersonMapper {
*
* &#64;Inject
* &#64;Named("org.examples.PersonMapperImpl_")
* private PersonMapper delegate;
*
* &#64;Override
* public PersonDto personToPersonDto(Person person) {
* PersonDto dto = delegate.personToPersonDto( person );
* dto.setName( person.getFirstName() + " " + person.getLastName() );
*
* return dto;
* }
* }
* </pre>
*
* <h4>Using the decorated mapper in the decorator</h4>
* <p>
* Unlike with the other component models, the usage site must be aware if a mapper is decorated or not, as for
* decorated mappers, the parameterless {@code @Named} annotation must be added to select the <em>decorator</em> to be
* injected:
*
* <pre>
* &#64;Inject
* &#64;Named
* private PersonMapper personMapper; // injects the decorator, with the injected original mapper
* </pre>
* <p>
* <b>NOTE:</b> The decorator feature is considered experimental and it may change in future releases. Currently
* decorators are only supported for the default component model, not for the CDI and Spring models.
*
* @author Gunnar Morling
*/
@ -45,10 +162,10 @@ import java.lang.annotation.Target;
public @interface DecoratedWith {
/**
* The decorator type. Must extend or implement the mapper type to which it is applied.
* The decorator type. Must be an abstract class that extends or implements the mapper type to which it is applied.
* <p>
* The decorator type must either have a default constructor or a constructor with a single parameter accepting the
* type of the decorated mapper.
* For component-model {@code default}, the decorator type must either have a default constructor or a constructor
* with a single parameter accepting the type of the decorated mapper.
*
* @return the decorator type
*/

View File

@ -26,4 +26,6 @@ import org.mapstruct.itest.cdi.other.DateMapper;
public interface DecoratedSourceTargetMapper {
Target sourceToTarget(Source source);
Target undecoratedSourceToTarget(Source source);
}

View File

@ -23,15 +23,12 @@ import javax.decorator.Delegate;
import javax.inject.Inject;
@Decorator
public class SourceTargetMapperDecorator implements DecoratedSourceTargetMapper {
public abstract class SourceTargetMapperDecorator implements DecoratedSourceTargetMapper {
@Delegate
@Inject
private DecoratedSourceTargetMapper delegate;
public SourceTargetMapperDecorator() {
}
@Override
public Target sourceToTarget(Source source) {
Target t = delegate.sourceToTarget( source );

View File

@ -0,0 +1,32 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.itest.jsr330;
import org.mapstruct.Mapper;
import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.jsr330.other.DateMapper;
@Mapper(componentModel = "jsr330", uses = DateMapper.class)
@DecoratedWith(SourceTargetMapperDecorator.class)
public interface DecoratedSourceTargetMapper {
Target sourceToTarget(Source source);
Target undecoratedSourceToTarget(Source source);
}

View File

@ -0,0 +1,32 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.itest.jsr330;
import org.mapstruct.Mapper;
import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.jsr330.other.DateMapper;
@Mapper(componentModel = "jsr330", uses = DateMapper.class)
@DecoratedWith(SecondSourceTargetMapperDecorator.class)
public interface SecondDecoratedSourceTargetMapper {
Target sourceToTarget(Source source);
Target undecoratedSourceToTarget(Source source);
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.itest.jsr330;
import javax.inject.Inject;
import javax.inject.Named;
public abstract class SecondSourceTargetMapperDecorator implements SecondDecoratedSourceTargetMapper {
@Inject
@Named("org.mapstruct.itest.jsr330.SecondDecoratedSourceTargetMapperImpl_")
private SecondDecoratedSourceTargetMapper delegate;
@Override
public Target sourceToTarget(Source source) {
Target t = delegate.sourceToTarget( source );
t.setFoo( 43L );
return t;
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.itest.jsr330;
import javax.inject.Inject;
import javax.inject.Named;
public abstract class SourceTargetMapperDecorator implements DecoratedSourceTargetMapper {
@Inject
@Named("org.mapstruct.itest.jsr330.DecoratedSourceTargetMapperImpl_")
private DecoratedSourceTargetMapper delegate;
@Override
public Target sourceToTarget(Source source) {
Target t = delegate.sourceToTarget( source );
t.setFoo( 43L );
return t;
}
}

View File

@ -18,9 +18,8 @@
*/
package org.mapstruct.itest.jsr330;
import static org.fest.assertions.Assertions.assertThat;
import javax.inject.Inject;
import javax.inject.Named;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -30,13 +29,15 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.fest.assertions.Assertions.assertThat;
/**
* Test for generation of JSR-330-based Mapper implementations
*
* @author Andreas Gudian
*/
@ContextConfiguration(classes = SpringTestConfig.class )
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration(classes = SpringTestConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Jsr330BasedMapperTest {
@Configuration
@ComponentScan(basePackageClasses = Jsr330BasedMapperTest.class)
@ -46,9 +47,16 @@ public class Jsr330BasedMapperTest {
@Inject
private SourceTargetMapper mapper;
@Inject
@Named
private DecoratedSourceTargetMapper decoratedMapper;
@Inject
@Named
private SecondDecoratedSourceTargetMapper secondDecoratedMapper;
@Test
public void shouldCreateSpringBasedMapper() {
public void shouldInjectJsr330BasedMapper() {
Source source = new Source();
Target target = mapper.sourceToTarget( source );
@ -57,4 +65,38 @@ public class Jsr330BasedMapperTest {
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
}
@Test
public void shouldInjectDecorator() {
Source source = new Source();
Target target = decoratedMapper.sourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 43 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
target = decoratedMapper.undecoratedSourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
}
@Test
public void shouldInjectSecondDecorator() {
Source source = new Source();
Target target = secondDecoratedMapper.sourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 43 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
target = secondDecoratedMapper.undecoratedSourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
}
}

View File

@ -27,4 +27,6 @@ import org.mapstruct.itest.spring.other.DateMapper;
public interface DecoratedSourceTargetMapper {
Target sourceToTarget(Source source);
Target undecoratedSourceToTarget(Source source);
}

View File

@ -0,0 +1,32 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.itest.spring;
import org.mapstruct.Mapper;
import org.mapstruct.DecoratedWith;
import org.mapstruct.itest.spring.other.DateMapper;
@Mapper( componentModel = "spring", uses = DateMapper.class )
@DecoratedWith( SecondSourceTargetMapperDecorator.class )
public interface SecondDecoratedSourceTargetMapper {
Target sourceToTarget(Source source);
Target undecoratedSourceToTarget(Source source);
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.itest.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
public abstract class SecondSourceTargetMapperDecorator implements SecondDecoratedSourceTargetMapper {
@Autowired
@Qualifier( "delegate" )
private DecoratedSourceTargetMapper delegate;
@Override
public Target sourceToTarget(Source source) {
Target t = delegate.sourceToTarget( source );
t.setFoo( 43L );
return t;
}
}

View File

@ -20,20 +20,13 @@ package org.mapstruct.itest.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
public class SourceTargetMapperDecorator implements DecoratedSourceTargetMapper {
public abstract class SourceTargetMapperDecorator implements DecoratedSourceTargetMapper {
@Autowired
@Qualifier( "delegate" )
private DecoratedSourceTargetMapper delegate;
public SourceTargetMapperDecorator() {
}
@Override
public Target sourceToTarget(Source source) {
Target t = delegate.sourceToTarget( source );

View File

@ -18,25 +18,24 @@
*/
package org.mapstruct.itest.spring;
import static org.fest.assertions.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.itest.spring.SpringBasedMapperTest.SpringTestConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.fest.assertions.Assertions.assertThat;
/**
* Test for generation of Spring-based Mapper implementations
*
* @author Andreas Gudian
*/
@ContextConfiguration(classes = SpringTestConfig.class )
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration(classes = SpringTestConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringBasedMapperTest {
@Configuration
@ -50,8 +49,11 @@ public class SpringBasedMapperTest {
@Autowired
private DecoratedSourceTargetMapper decoratedMapper;
@Autowired
private SecondDecoratedSourceTargetMapper secondDecoratedMapper;
@Test
public void shouldCreateSpringBasedMapper() {
public void shouldInjectSpringBasedMapper() {
Source source = new Source();
Target target = mapper.sourceToTarget( source );
@ -69,5 +71,29 @@ public class SpringBasedMapperTest {
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 43 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
target = decoratedMapper.undecoratedSourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
}
@Test
public void shouldInjectSecondDecorator() {
Source source = new Source();
Target target = secondDecoratedMapper.sourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 43 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
target = secondDecoratedMapper.undecoratedSourceToTarget( source );
assertThat( target ).isNotNull();
assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) );
assertThat( target.getDate() ).isEqualTo( "1980" );
}
}

View File

@ -179,6 +179,12 @@
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!-- Joda-Time -->
<dependency>
<groupId>joda-time</groupId>

View File

@ -85,6 +85,23 @@
<scope>test</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<!-- There is no compile dependency to Joda-Time; It's only required for testing the Joda conversions -->
<dependency>
<groupId>joda-time</groupId>

View File

@ -18,10 +18,11 @@
*/
package org.mapstruct.ap.internal.model;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.util.Collections;
/**
* Mapper reference which is retrieved via Annotation-based dependency injection.
@ -31,19 +32,26 @@ import org.mapstruct.ap.internal.util.Collections;
*/
public class AnnotationMapperReference extends MapperReference {
private final Annotation annotation;
private final List<Annotation> annotations;
public AnnotationMapperReference(Type type, String variableName, Annotation annotation, boolean isUsed) {
public AnnotationMapperReference(Type type, String variableName, List<Annotation> annotations, boolean isUsed) {
super( type, variableName, isUsed );
this.annotation = annotation;
this.annotations = annotations;
}
public Annotation getAnnotation() {
return annotation;
public List<Annotation> getAnnotations() {
return annotations;
}
@Override
public Set<Type> getImportTypes() {
return Collections.asSet( annotation.getImportTypes(), super.getType() );
Set<Type> types = new HashSet<Type>();
types.add( getType() );
for ( Annotation annotation : annotations ) {
types.addAll( annotation.getImportTypes() );
}
return types;
}
}

View File

@ -50,14 +50,14 @@ public abstract class GeneratedType extends ModelElement {
private final List<Annotation> annotations;
private final List<MappingMethod> methods;
private final List<? extends Field> fields;
private final SortedSet<Type> extraImportedTypes;
private final boolean suppressGeneratorTimestamp;
private final boolean suppressGeneratorVersionComment;
private final VersionInformation versionInformation;
private final Accessibility accessibility;
private final Constructor constructor;
private List<? extends Field> fields;
private Constructor constructor;
/**
* Type representing the {@code @Generated} annotation
@ -128,10 +128,14 @@ public abstract class GeneratedType extends ModelElement {
return methods;
}
public List<? extends ModelElement> getFields() {
public List<? extends Field> getFields() {
return fields;
}
public void setFields(List<? extends Field> fields) {
this.fields = fields;
}
public boolean isSuppressGeneratorTimestamp() {
return suppressGeneratorTimestamp;
}
@ -182,6 +186,10 @@ public abstract class GeneratedType extends ModelElement {
return constructor;
}
public void removeConstructor() {
constructor = null;
}
protected void addWithDependents(Collection<Type> collection, Type typeToAdd) {
if ( typeToAdd == null ) {
return;

View File

@ -18,6 +18,8 @@
*/
package org.mapstruct.ap.internal.processor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
@ -25,6 +27,8 @@ import javax.lang.model.element.TypeElement;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.AnnotationMapperReference;
import org.mapstruct.ap.internal.model.Decorator;
import org.mapstruct.ap.internal.model.Field;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.MapperReference;
import org.mapstruct.ap.internal.model.common.TypeFactory;
@ -64,27 +68,55 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
if ( !requiresGenerationOfDecoratorClass() ) {
mapper.removeDecorator();
}
else if ( mapper.getDecorator() != null ) {
adjustDecorator( mapper );
}
List<Annotation> annotations = getMapperReferenceAnnotations();
ListIterator<MapperReference> iterator = mapper.getReferencedMappers().listIterator();
while ( iterator.hasNext() ) {
MapperReference reference = iterator.next();
iterator.remove();
iterator.add( replacementMapperReference( reference ) );
iterator.add( replacementMapperReference( reference, annotations ) );
}
return mapper;
}
protected void adjustDecorator(Mapper mapper) {
Decorator decorator = mapper.getDecorator();
for ( Annotation typeAnnotation : getDecoratorAnnotations() ) {
decorator.addAnnotation( typeAnnotation );
}
decorator.removeConstructor();
List<Annotation> annotations = getDelegatorReferenceAnnotations( mapper );
List<Field> replacement = new ArrayList<Field>();
if ( !decorator.getMethods().isEmpty() ) {
for ( Field field : decorator.getFields() ) {
replacement.add( replacementMapperReference( field, annotations ) );
}
}
decorator.setFields( replacement );
}
protected List<Annotation> getDelegatorReferenceAnnotations(Mapper mapper) {
return Collections.emptyList();
}
/**
* @param originalReference the reference to be replaced
*
* @return the mapper reference replacing the original one
*/
protected MapperReference replacementMapperReference(MapperReference originalReference) {
protected MapperReference replacementMapperReference(Field originalReference, List<Annotation> annotations) {
return new AnnotationMapperReference(
originalReference.getType(),
originalReference.getVariableName(),
getMapperReferenceAnnotation(),
annotations,
originalReference.isUsed()
);
}
@ -99,10 +131,17 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle
*/
protected abstract List<Annotation> getTypeAnnotations(Mapper mapper);
/**
* @return the annotation(s) to be added at the decorator of the mapper
*/
protected List<Annotation> getDecoratorAnnotations() {
return Collections.emptyList();
}
/**
* @return the annotation of the field for the mapper reference
*/
protected abstract Annotation getMapperReferenceAnnotation();
protected abstract List<Annotation> getMapperReferenceAnnotations();
/**
* @return if a decorator (sub-)class needs to be generated or not

View File

@ -18,6 +18,7 @@
*/
package org.mapstruct.ap.internal.processor;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -46,8 +47,8 @@ public class CdiComponentProcessor extends AnnotationBasedComponentModelProcesso
}
@Override
protected Annotation getMapperReferenceAnnotation() {
return new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) );
protected List<Annotation> getMapperReferenceAnnotations() {
return Arrays.asList( new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) ) );
}
@Override

View File

@ -18,11 +18,13 @@
*/
package org.mapstruct.ap.internal.processor;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.mapstruct.ap.internal.model.Annotation;
import org.mapstruct.ap.internal.model.Mapper;
/**
* A {@link ModelElementProcessor} which converts the given {@link Mapper}
* object into a JSR 330 style bean in case "jsr330" is configured as the
@ -39,16 +41,46 @@ public class Jsr330ComponentProcessor extends AnnotationBasedComponentModelProce
@Override
protected List<Annotation> getTypeAnnotations(Mapper mapper) {
return Collections.singletonList( new Annotation( getTypeFactory().getType( "javax.inject.Named" ) ) );
if ( mapper.getDecorator() == null ) {
return Collections.singletonList( named() );
}
else {
return Collections.singletonList( namedDelegate( mapper ) );
}
}
@Override
protected Annotation getMapperReferenceAnnotation() {
return new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) );
protected List<Annotation> getDecoratorAnnotations() {
return Collections.singletonList( named() );
}
@Override
protected List<Annotation> getDelegatorReferenceAnnotations(Mapper mapper) {
return Arrays.asList( inject(), namedDelegate( mapper ) );
}
@Override
protected List<Annotation> getMapperReferenceAnnotations() {
return Collections.singletonList( inject() );
}
@Override
protected boolean requiresGenerationOfDecoratorClass() {
return true;
}
private Annotation named() {
return new Annotation( getTypeFactory().getType( "javax.inject.Named" ) );
}
private Annotation namedDelegate(Mapper mapper) {
return new Annotation(
getTypeFactory().getType( "javax.inject.Named" ),
Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' )
);
}
private Annotation inject() {
return new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) );
}
}

View File

@ -20,6 +20,7 @@ package org.mapstruct.ap.internal.processor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.mapstruct.ap.internal.model.Annotation;
@ -42,26 +43,58 @@ public class SpringComponentProcessor extends AnnotationBasedComponentModelProce
@Override
protected List<Annotation> getTypeAnnotations(Mapper mapper) {
List<Annotation> typeAnnotations = new ArrayList<Annotation>();
typeAnnotations.add( new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) ) );
typeAnnotations.add( component() );
if ( mapper.getDecorator() != null ) {
Annotation qualifier = new Annotation(
getTypeFactory().getType( "org.springframework.beans.factory.annotation.Qualifier" ),
Arrays.asList( "\"delegate\"" )
);
typeAnnotations.add( qualifier );
typeAnnotations.add( qualifierDelegate() );
}
return typeAnnotations;
}
@Override
protected Annotation getMapperReferenceAnnotation() {
return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Autowired" ) );
protected List<Annotation> getDecoratorAnnotations() {
return Arrays.asList(
component(),
primary()
);
}
@Override
protected List<Annotation> getMapperReferenceAnnotations() {
return Collections.singletonList(
autowired()
);
}
@Override
protected List<Annotation> getDelegatorReferenceAnnotations(Mapper mapper) {
return Arrays.asList(
autowired(),
qualifierDelegate()
);
}
@Override
protected boolean requiresGenerationOfDecoratorClass() {
return false;
return true;
}
private Annotation autowired() {
return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Autowired" ) );
}
private Annotation qualifierDelegate() {
return new Annotation(
getTypeFactory().getType( "org.springframework.beans.factory.annotation.Qualifier" ),
Collections.singletonList( "\"delegate\"" ) );
}
private Annotation primary() {
return new Annotation( getTypeFactory().getType( "org.springframework.context.annotation.Primary" ) );
}
private Annotation component() {
return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) );
}
}

View File

@ -18,5 +18,7 @@
limitations under the License.
-->
<#list annotations as annotation>
<#nt><@includeModel object=annotation/>
</#list>
private <@includeModel object=type/> ${variableName};

View File

@ -0,0 +1,110 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.decorator.jsr330;
import java.util.Calendar;
import javax.inject.Inject;
import javax.inject.Named;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
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.fest.assertions.Assertions.assertThat;
/**
* Test for the application of decorators using component model jsr330.
*
* @author Andreas Gudian
*/
@WithClasses({
Person.class,
PersonDto.class,
Address.class,
AddressDto.class,
PersonMapper.class,
PersonMapperDecorator.class
})
@IssueKey("592")
@RunWith(AnnotationProcessorTestRunner.class)
@ComponentScan(basePackageClasses = Jsr330DecoratorTest.class)
@Configuration
public class Jsr330DecoratorTest {
@Inject
@Named
private PersonMapper personMapper;
private ConfigurableApplicationContext context;
@Before
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@After
public void springDown() {
if ( context != null ) {
context.close();
}
}
@Test
public void shouldInvokeDecoratorMethods() {
//given
Calendar birthday = Calendar.getInstance();
birthday.set( 1928, 4, 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" );
}
@Test
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,37 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.decorator.jsr330;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
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 = "jsr330")
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
@Mapping( target = "name", ignore = true )
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.decorator.jsr330;
import javax.inject.Inject;
import javax.inject.Named;
import org.mapstruct.ap.test.decorator.Person;
import org.mapstruct.ap.test.decorator.PersonDto;
public abstract class PersonMapperDecorator implements PersonMapper {
@Inject
@Named("org.mapstruct.ap.test.decorator.jsr330.PersonMapperImpl_")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.decorator.spring;
import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
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 = "spring")
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
@Mapping( target = "name", ignore = true )
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.decorator.spring;
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 {
@Autowired
@Qualifier("delegate")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}

View File

@ -0,0 +1,107 @@
/**
* Copyright 2012-2015 Gunnar Morling (http://www.gunnarmorling.de/)
* and/or other contributors as indicated by the @authors tag. See the
* copyright.txt file in the distribution for a full listing of all
* contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mapstruct.ap.test.decorator.spring;
import java.util.Calendar;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
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.fest.assertions.Assertions.assertThat;
/**
* Test for the application of decorators using component model spring.
*
* @author Andreas Gudian
*/
@WithClasses({
Person.class,
PersonDto.class,
Address.class,
AddressDto.class,
PersonMapper.class,
PersonMapperDecorator.class
})
@IssueKey("592")
@RunWith(AnnotationProcessorTestRunner.class)
@ComponentScan(basePackageClasses = SpringDecoratorTest.class)
@Configuration
public class SpringDecoratorTest {
@Autowired
private PersonMapper personMapper;
private ConfigurableApplicationContext context;
@Before
public void springUp() {
context = new AnnotationConfigApplicationContext( getClass() );
context.getAutowireCapableBeanFactory().autowireBean( this );
}
@After
public void springDown() {
if ( context != null ) {
context.close();
}
}
@Test
public void shouldInvokeDecoratorMethods() {
//given
Calendar birthday = Calendar.getInstance();
birthday.set( 1928, 4, 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" );
}
@Test
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" );
}
}