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 extends Field> 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 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 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/>
+#list>
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" );
+ }
+}