diff --git a/core-common/src/main/java/org/mapstruct/DecoratedWith.java b/core-common/src/main/java/org/mapstruct/DecoratedWith.java index 5b48e7686..4f7500bf9 100644 --- a/core-common/src/main/java/org/mapstruct/DecoratedWith.java +++ b/core-common/src/main/java/org/mapstruct/DecoratedWith.java @@ -29,14 +29,131 @@ import java.lang.annotation.Target; *

* 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. + *

+ * NOTE: The usage of decorated mappers differs depending on the selected component model. + *

+ * NOTE: This annotation is not supported for the component model {@code cdi}. Use CDI's own + * {@code @Decorator} feature instead. + *

+ * NOTE: The decorator feature when used with component model {@code jsr330} is considered experimental + * and it may change in future releases. + *

+ *

Examples

+ *

+ * For the examples below, consider the following mapper declaration: + * + *

+ * @Mapper(componentModel = "...")
+ * @DecoratedWith(PersonMapperDecorator.class)
+ * public interface PersonMapper {
+ *
+ *     @Mapping(target = "name", ignore = true)
+ *     PersonDto personToPersonDto(Person person);
+ *
+ *     AddressDto addressToAddressDto(Address address); // not touched by the decorator
+ * }
+ * 
+ * + *

1. Component model 'default'

+ *

Referencing the original mapper in the decorator

*

* 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: + * + *

+ * public abstract class PersonMapperDecorator implements PersonMapper {
+ *
+ *     private PersonMapper delegate;
+ *
+ *     public PersonMapperDecorator(PersonMapper delegate) {
+ *         this.delegate = delegate;
+ *     }
+ *
+ *     @Override
+ *     public PersonDto personToPersonDto(Person person) {
+ *         PersonDto dto = delegate.personToPersonDto( person );
+ *         dto.setName( person.getFirstName() + " " + person.getLastName() );
+ *
+ *         return dto;
+ *     }
+ * }
+ * 
+ * + *

Using the decorated mapper

+ *

+ * Nothing special needs to be done. When using {@code Mappers.getMapper( PersonMapper.class )}, the decorator + * is returned, with the injected original mapper. + *

2. Component model 'spring'

+ *

Referencing the original mapper in the decorator

+ *

+ * 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: + * + *

+ * 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;
+ *     }
+ * }
+ * 
+ * + *

Using the decorated mapper in the decorator

+ *

+ * 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: + * + *

+ * @Autowired
+ * private PersonMapper personMapper; // injects the decorator, with the injected original mapper
+ * 
+ * + *

3. Component model 'jsr330'

+ *

Referencing the original mapper

+ *

+ * 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: + * + *

+ * public abstract class PersonMapperDecorator implements PersonMapper {
+ *
+ *     @Inject
+ *     @Named("org.examples.PersonMapperImpl_")
+ *     private PersonMapper delegate;
+ *
+ *     @Override
+ *     public PersonDto personToPersonDto(Person person) {
+ *         PersonDto dto = delegate.personToPersonDto( person );
+ *         dto.setName( person.getFirstName() + " " + person.getLastName() );
+ *
+ *         return dto;
+ *     }
+ * }
+ * 
+ * + *

Using the decorated mapper in the decorator

+ *

+ * 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 decorator to be + * injected: + * + *

+ * @Inject
+ * @Named
+ * private PersonMapper personMapper; // injects the decorator, with the injected original mapper
+ * 
*

- * NOTE: 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. *

- * 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 */ diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java index c8ea3e3e7..34ac84d2c 100644 --- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java +++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/DecoratedSourceTargetMapper.java @@ -26,4 +26,6 @@ import org.mapstruct.itest.cdi.other.DateMapper; public interface DecoratedSourceTargetMapper { Target sourceToTarget(Source source); + + Target undecoratedSourceToTarget(Source source); } diff --git a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java index 25f699a82..57f9724af 100644 --- a/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapperDecorator.java @@ -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 ); diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/DecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/DecoratedSourceTargetMapper.java new file mode 100644 index 000000000..c1ea2d8ef --- /dev/null +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/DecoratedSourceTargetMapper.java @@ -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); +} diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondDecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondDecoratedSourceTargetMapper.java new file mode 100644 index 000000000..8feef2aa6 --- /dev/null +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondDecoratedSourceTargetMapper.java @@ -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); +} diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java new file mode 100644 index 000000000..cfebaeba5 --- /dev/null +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java @@ -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; + } +} diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java new file mode 100644 index 000000000..cd3d9810d --- /dev/null +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java @@ -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; + } +} diff --git a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java index 984152da1..ca8473499 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java +++ b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java @@ -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" ); + } } diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java index 67330d3d8..f80d98a71 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/DecoratedSourceTargetMapper.java @@ -27,4 +27,6 @@ import org.mapstruct.itest.spring.other.DateMapper; public interface DecoratedSourceTargetMapper { Target sourceToTarget(Source source); + + Target undecoratedSourceToTarget(Source source); } diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondDecoratedSourceTargetMapper.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondDecoratedSourceTargetMapper.java new file mode 100644 index 000000000..b179d89b1 --- /dev/null +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondDecoratedSourceTargetMapper.java @@ -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); +} diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondSourceTargetMapperDecorator.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondSourceTargetMapperDecorator.java new file mode 100644 index 000000000..a33ec59d4 --- /dev/null +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SecondSourceTargetMapperDecorator.java @@ -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; + } +} diff --git a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java index 6ac7fe3cb..7f2a85ecd 100644 --- a/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapperDecorator.java @@ -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 ); diff --git a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java index 7a75a4108..596dad777 100644 --- a/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java +++ b/integrationtest/src/test/resources/springTest/src/test/java/org/mapstruct/itest/spring/SpringBasedMapperTest.java @@ -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" ); } } diff --git a/parent/pom.xml b/parent/pom.xml index 883b8a847..bb1ad5de9 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -179,6 +179,12 @@ ${org.springframework.version} + + commons-logging + commons-logging + 1.1.3 + + joda-time diff --git a/processor/pom.xml b/processor/pom.xml index 9071a0d83..a22c6a136 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -85,6 +85,23 @@ test + + + org.springframework + spring-test + test + + + org.springframework + spring-beans + test + + + org.springframework + spring-context + test + + joda-time diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java index 37536633f..518b5394a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java @@ -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 annotations; - public AnnotationMapperReference(Type type, String variableName, Annotation annotation, boolean isUsed) { + public AnnotationMapperReference(Type type, String variableName, List annotations, boolean isUsed) { super( type, variableName, isUsed ); - this.annotation = annotation; + this.annotations = annotations; } - public Annotation getAnnotation() { - return annotation; + public List getAnnotations() { + return annotations; } @Override public Set getImportTypes() { - return Collections.asSet( annotation.getImportTypes(), super.getType() ); + Set types = new HashSet(); + types.add( getType() ); + + for ( Annotation annotation : annotations ) { + types.addAll( annotation.getImportTypes() ); + } + + return types; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index e09386322..bebde7fa8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -50,14 +50,14 @@ public abstract class GeneratedType extends ModelElement { private final List annotations; private final List methods; - private final List fields; private final SortedSet extraImportedTypes; private final boolean suppressGeneratorTimestamp; private final boolean suppressGeneratorVersionComment; private final VersionInformation versionInformation; private final Accessibility accessibility; - private final Constructor constructor; + private List fields; + private Constructor constructor; /** * Type representing the {@code @Generated} annotation @@ -128,10 +128,14 @@ public abstract class GeneratedType extends ModelElement { return methods; } - public List getFields() { + public List getFields() { return fields; } + public void setFields(List 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 collection, Type typeToAdd) { if ( typeToAdd == null ) { return; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index 3e2b89a5e..473528849 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -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 annotations = getMapperReferenceAnnotations(); ListIterator 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 annotations = getDelegatorReferenceAnnotations( mapper ); + List replacement = new ArrayList(); + if ( !decorator.getMethods().isEmpty() ) { + for ( Field field : decorator.getFields() ) { + replacement.add( replacementMapperReference( field, annotations ) ); + } + } + decorator.setFields( replacement ); + } + + protected List 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 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 getTypeAnnotations(Mapper mapper); + /** + * @return the annotation(s) to be added at the decorator of the mapper + */ + protected List getDecoratorAnnotations() { + return Collections.emptyList(); + } + /** * @return the annotation of the field for the mapper reference */ - protected abstract Annotation getMapperReferenceAnnotation(); + protected abstract List getMapperReferenceAnnotations(); /** * @return if a decorator (sub-)class needs to be generated or not diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java index 14f92ea69..a9f4ebf1d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java @@ -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 getMapperReferenceAnnotations() { + return Arrays.asList( new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) ) ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java index 3bf6ce4bb..c140e4bcb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java @@ -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 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 getDecoratorAnnotations() { + return Collections.singletonList( named() ); + } + + @Override + protected List getDelegatorReferenceAnnotations(Mapper mapper) { + return Arrays.asList( inject(), namedDelegate( mapper ) ); + } + + @Override + protected List 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" ) ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java index 99d609f83..8d1be5b30 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java @@ -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 getTypeAnnotations(Mapper mapper) { List typeAnnotations = new ArrayList(); - 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 getDecoratorAnnotations() { + return Arrays.asList( + component(), + primary() + ); + } + + @Override + protected List getMapperReferenceAnnotations() { + return Collections.singletonList( + autowired() + ); + } + + @Override + protected List 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" ) ); } } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl index 1898e51ac..8a1de2400 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl @@ -18,5 +18,7 @@ limitations under the License. --> +<#list annotations as annotation> <#nt><@includeModel object=annotation/> + private <@includeModel object=type/> ${variableName}; \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java new file mode 100644 index 000000000..758adea47 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -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" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapper.java new file mode 100644 index 000000000..37a2e253d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapperDecorator.java new file mode 100644 index 000000000..1954148d1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapperDecorator.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java new file mode 100644 index 000000000..73089b94f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java new file mode 100644 index 000000000..7a9863e00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java new file mode 100644 index 000000000..33968c6cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java @@ -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" ); + } +}