diff --git a/README.md b/README.md index 412a0595..5b123f52 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Currently, the following examples exist: * _mapstruct-jpa-parent-child_: Example on how to use @Context in relation to parent / child relations in JPA) * _mapstruct-suppress-unmapped_: Shows how mapping to target properties can be ignored without warning by default in a mixed scenario. However bean property mappings that have the same name will still be applied. * _mapstruct-lookup-entity-with-composed-key_: Shows how an object with composite key can be read from the database in a mapping method. +* _mapstruct-metadata-annotations_: Demonstrates how to read annotations and use them as mapping instruction. ## License diff --git a/mapstruct-metadata-with-annotations/pom.xml b/mapstruct-metadata-with-annotations/pom.xml new file mode 100644 index 00000000..430b08c2 --- /dev/null +++ b/mapstruct-metadata-with-annotations/pom.xml @@ -0,0 +1,76 @@ + + + + 4.0.0 + org.mapstruct.examples.metadata + mapstruct-examples-metadata-with-annotations + 1.0.0 + + + 1.3.0.Beta1 + + + + + org.mapstruct + mapstruct + ${org.mapstruct.version} + + + + junit + junit + 4.12 + test + + + + org.assertj + assertj-core + 3.9.0 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.2 + + 1.8 + 1.8 + + + org.mapstruct + mapstruct-processor + ${org.mapstruct.version} + + + + + + + + + diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/dto/LegalEntity.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/dto/LegalEntity.java new file mode 100644 index 00000000..e2064e51 --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/dto/LegalEntity.java @@ -0,0 +1,57 @@ +/** + * Copyright 2012-2018 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.example.metadata.dto; + +/** + * + * @author Sjaak Derksen + */ +public class LegalEntity { + + private String name; + + private String address; + + private String id; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/dto/TaxRecord.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/dto/TaxRecord.java new file mode 100644 index 00000000..6d3b0bd9 --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/dto/TaxRecord.java @@ -0,0 +1,57 @@ +/** + * Copyright 2012-2018 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.example.metadata.dto; + +/** + * + * @author Sjaak Derksen + */ +public class TaxRecord { + + private int number; + + private int year; + + private LegalEntity legalEntity; + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public LegalEntity getLegalEntity() { + return legalEntity; + } + + public void setLegalEntity(LegalEntity legalEntity) { + this.legalEntity = legalEntity; + } + +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/LegalEntityPE.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/LegalEntityPE.java new file mode 100644 index 00000000..a2da553d --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/LegalEntityPE.java @@ -0,0 +1,48 @@ +/** + * Copyright 2012-2018 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.example.metadata.entities; + + +/** + * + * @author Sjaak Derksen + */ +public class LegalEntityPE { + + private String name; + + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/OrganisationPE.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/OrganisationPE.java new file mode 100644 index 00000000..92004a73 --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/OrganisationPE.java @@ -0,0 +1,37 @@ +/** + * Copyright 2012-2018 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.example.metadata.entities; + +/** + * + * @author Sjaak Derksen + */ +public class OrganisationPE extends LegalEntityPE { + + private String chamberOfCommerceNumber; + + public String getChamberOfCommerceNumber() { + return chamberOfCommerceNumber; + } + + public void setChamberOfCommerceNumber(String chamberOfCommerceNumber) { + this.chamberOfCommerceNumber = chamberOfCommerceNumber; + } + +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/PersonPE.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/PersonPE.java new file mode 100644 index 00000000..ff9040da --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/PersonPE.java @@ -0,0 +1,38 @@ +/** + * Copyright 2012-2018 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.example.metadata.entities; + + +/** + * + * @author Sjaak Derksen + */ +public class PersonPE extends LegalEntityPE { + + private String socialSecurityNumber; + + public String getSocialSecurityNumber() { + return socialSecurityNumber; + } + + public void setSocialSecurityNumber(String socialSecurityNumber) { + this.socialSecurityNumber = socialSecurityNumber; + } + +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/TaxRecordPE.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/TaxRecordPE.java new file mode 100644 index 00000000..d1ef7b76 --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/entities/TaxRecordPE.java @@ -0,0 +1,60 @@ +/** + * Copyright 2012-2018 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.example.metadata.entities; + +import org.mapstruct.example.metadata.mapper.annotations.Treatment; + +/** + * + * @author Sjaak Derksen + */ +public class TaxRecordPE { + + private int number; + + private int year; + + private LegalEntityPE legalEntity; + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public LegalEntityPE getLegalEntity() { + return legalEntity; + } + + @Treatment( formatAs = "organisation" ) + public void setLegalEntity(LegalEntityPE legalEntity) { + this.legalEntity = legalEntity; + } + +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/SourceTargetMapper.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/SourceTargetMapper.java new file mode 100644 index 00000000..ad10e8dd --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/SourceTargetMapper.java @@ -0,0 +1,71 @@ +/** + * Copyright 2012-2018 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.example.metadata.mapper; + +import org.mapstruct.example.metadata.mapper.util.TaxContext; +import java.lang.annotation.Annotation; +import java.util.List; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.example.metadata.dto.LegalEntity; +import org.mapstruct.example.metadata.dto.TaxRecord; +import org.mapstruct.example.metadata.entities.LegalEntityPE; +import org.mapstruct.example.metadata.entities.OrganisationPE; +import org.mapstruct.example.metadata.entities.PersonPE; +import org.mapstruct.example.metadata.entities.TaxRecordPE; +import org.mapstruct.example.metadata.mapper.annotations.DoNotSelectForMapping; +import org.mapstruct.example.metadata.mapper.annotations.Treatment; +import org.mapstruct.factory.Mappers; + + + + +@Mapper +public abstract class SourceTargetMapper { + + public static final SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class ); + + public abstract TaxRecordPE toTarget( TaxRecord s, @Context TaxContext ctx ); + + protected LegalEntityPE convert( LegalEntity s, @Context TaxContext ctx ) { + + List annotations = ctx.getAnnotationsForMethod( "setLegalEntity" ); + String formatAs = annotations.stream() + .filter( Treatment.class::isInstance ) + .map( Treatment.class::cast ) + .findFirst() + .map( Treatment::formatAs ) + .orElse( "person" ); + if ( "organisation".equals( formatAs ) ) { + return mapOrganisation( s ); + } + else { + return mapPerson( s ); + } + } + + @DoNotSelectForMapping + @Mapping( target = "socialSecurityNumber", source = "id" ) + protected abstract PersonPE mapPerson( LegalEntity s ); + + @DoNotSelectForMapping + @Mapping( target = "chamberOfCommerceNumber", source = "id" ) + protected abstract OrganisationPE mapOrganisation( LegalEntity s ); +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/annotations/DoNotSelectForMapping.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/annotations/DoNotSelectForMapping.java new file mode 100644 index 00000000..acfebe42 --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/annotations/DoNotSelectForMapping.java @@ -0,0 +1,35 @@ +/** + * Copyright 2012-2018 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.example.metadata.mapper.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.mapstruct.Qualifier; + +/** + * This is a qualfier with the opposite purpose. It is never used in an @Mapping and + * its purpose is to avoid selection of a method annotated with his qualifier + */ +@Qualifier +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.SOURCE) +public @interface DoNotSelectForMapping { +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/annotations/Treatment.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/annotations/Treatment.java new file mode 100644 index 00000000..29d38fad --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/annotations/Treatment.java @@ -0,0 +1,31 @@ +/** + * Copyright 2012-2018 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.example.metadata.mapper.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Treatment { + + String formatAs() default "person"; +} diff --git a/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/util/TaxContext.java b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/util/TaxContext.java new file mode 100644 index 00000000..31874b84 --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/main/java/org/mapstruct/example/metadata/mapper/util/TaxContext.java @@ -0,0 +1,51 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.mapstruct.example.metadata.mapper.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.mapstruct.BeforeMapping; +import org.mapstruct.TargetType; + +/** + * + * @author Sjaak Derksen + */ +public class TaxContext { + + private final Map> annotations = new HashMap<>(); + + @BeforeMapping + public void registerAnnotationsForTarget(@TargetType Class t) { + /* Although MapStruct is all about generating code, there is currently + * no means to analyse annotations runtime without reflection. One could + * even argue whether this is should be part of a mapping framework such + * as MapStruct. Hence, reflection is used in this particular place, in this + * example to demonstrate how to do this. + */ + Method[] methods = t.getMethods(); + for ( Method method : methods ) { + for ( Annotation annotation : method.getAnnotations() ) { + registerAnnotationForMethod( method.getName(), annotation ); + } + } + } + + private void registerAnnotationForMethod(String methodName, Annotation annotation) { + if ( !annotations.containsKey( methodName ) ) { + annotations.put( methodName, new ArrayList<>() ); + } + annotations.get( methodName ).add( annotation ); + } + + public List getAnnotationsForMethod(String methodName) { + return annotations.getOrDefault( methodName, new ArrayList<>() ); + } +} diff --git a/mapstruct-metadata-with-annotations/src/test/java/org/mapstruct/example/SourceTargetMapperTest.java b/mapstruct-metadata-with-annotations/src/test/java/org/mapstruct/example/SourceTargetMapperTest.java new file mode 100644 index 00000000..d18bea60 --- /dev/null +++ b/mapstruct-metadata-with-annotations/src/test/java/org/mapstruct/example/SourceTargetMapperTest.java @@ -0,0 +1,70 @@ +/** + * Copyright 2012-2018 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.example; + +import static org.assertj.core.api.Assertions.assertThat; +import org.mapstruct.example.metadata.mapper.SourceTargetMapper; +import org.junit.Test; +import org.mapstruct.example.metadata.dto.LegalEntity; +import org.mapstruct.example.metadata.dto.TaxRecord; +import org.mapstruct.example.metadata.entities.OrganisationPE; +import org.mapstruct.example.metadata.entities.TaxRecordPE; +import org.mapstruct.example.metadata.mapper.util.TaxContext; + +/** + * + * @author Sjaak Derksen + */ +public class SourceTargetMapperTest { + + public SourceTargetMapperTest() { + } + + /** + * Test of toTarget method, of class SourceTargetMapper. + */ + @Test + public void testToTarget() { + + LegalEntity legalEntity = new LegalEntity(); + legalEntity.setName( "ACME" ); + legalEntity.setAddress( "Tinseltown" ); + legalEntity.setId( "123.4567" ); + TaxRecord taxRecord = new TaxRecord(); + taxRecord.setNumber( 1 ); + taxRecord.setYear( 2017 ); + taxRecord.setLegalEntity( legalEntity ); + + TaxRecordPE taxRecordPE = SourceTargetMapper.MAPPER.toTarget( taxRecord, new TaxContext() ); + + assertThat( taxRecordPE ).isNotNull(); + assertThat( taxRecordPE.getNumber() ).isEqualTo( 1 ); + assertThat( taxRecordPE.getYear() ).isEqualTo( 2017 ); + assertThat( taxRecordPE.getLegalEntity() ).isNotNull(); + assertThat( taxRecordPE.getLegalEntity().getName() ).isEqualTo( "ACME" ); + assertThat( taxRecordPE.getLegalEntity().getAddress() ).isEqualTo( "Tinseltown" ); + // check whether annotation analysis worked + assertThat( taxRecordPE.getLegalEntity() ).isInstanceOf( OrganisationPE.class ); + OrganisationPE organisation = (OrganisationPE) taxRecordPE.getLegalEntity(); + assertThat( ( organisation ).getChamberOfCommerceNumber() ).isEqualTo( "123.4567" ); + } + + + +} diff --git a/pom.xml b/pom.xml index e0cb75c2..88d7390a 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ mapstruct-kotlin mapstruct-jpa-child-parent mapstruct-lookup-entity-with-id + mapstruct-metadata-with-annotations mapstruct-suppress-unmapped