mapstruct/documentation/src/main/asciidoc/chapter-13-using-mapstruct-spi.asciidoc
Filip Hrisafov 7dcbef349d
#2169 Add support for defining a custom unexpected value mapping exception
Expose definition via:

* `@EnumMapping`
* `@Mapper`
* `@MapperConfig`
* `EnumMappingStrategy` SPI

Rename `EnumNamingStrategy` to `EnumMappingStrategy`
2020-08-29 13:53:30 +02:00

362 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[[using-spi]]
== Using the MapStruct SPI
=== Custom Accessor Naming Strategy
MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provider Interface (SPI). A nice example is the use of the fluent API on the source object `GolfPlayer` and `GolfPlayerDto` below.
.Source object GolfPlayer with fluent API.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class GolfPlayer {
private double handicap;
private String name;
public double handicap() {
return handicap;
}
public GolfPlayer withHandicap(double handicap) {
this.handicap = handicap;
return this;
}
public String name() {
return name;
}
public GolfPlayer withName(String name) {
this.name = name;
return this;
}
}
----
====
.Source object GolfPlayerDto with fluent API.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class GolfPlayerDto {
private double handicap;
private String name;
public double handicap() {
return handicap;
}
public GolfPlayerDto withHandicap(double handicap) {
this.handicap = handicap;
return this;
}
public String name() {
return name;
}
public GolfPlayerDto withName(String name) {
this.name = name;
return this
}
}
----
====
We want `GolfPlayer` to be mapped to a target object `GolfPlayerDto` similar like we 'always' do this:
.Source object with fluent API.
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface GolfPlayerMapper {
GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class );
GolfPlayerDto toDto(GolfPlayer player);
GolfPlayer toPlayer(GolfPlayerDto player);
}
----
====
This can be achieved with implementing the SPI `org.mapstruct.ap.spi.AccessorNamingStrategy` as in the following example. Here's an implemented `org.mapstruct.ap.spi.AccessorNamingStrategy`:
.CustomAccessorNamingStrategy
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
/**
* A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the
* form of {@code withProperty(value)}.
*/
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
@Override
public boolean isGetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID;
}
@Override
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith( "with" ) && methodName.length() > 4;
}
@Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
return IntrospectorUtils.decapitalize( methodName.startsWith( "with" ) ? methodName.substring( 4 ) : methodName );
}
}
----
====
The `CustomAccessorNamingStrategy` makes use of the `DefaultAccessorNamingStrategy` (also available in mapstruct-processor) and relies on that class to leave most of the default behaviour unchanged.
To use a custom SPI implementation, it must be located in a separate JAR file together with the file `META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar).
[TIP]
Fore more details: The example above is present in our examples repository (https://github.com/mapstruct/mapstruct-examples).
[mapping-exclusion-provider]
=== Mapping Exclusion Provider
MapStruct offers the possibility to override the `MappingExclusionProvider` via the Service Provider Interface (SPI).
A nice example is to not allow MapStruct to create an automatic sub-mapping for a certain type,
i.e. MapStruct will not try to generate an automatic sub-mapping method for an excluded type.
[NOTE]
====
The `DefaultMappingExclusionProvider` will exclude all types under the `java` or `javax` packages.
This means that MapStruct will not try to generate an automatic sub-mapping method between some custom type and some type declared in the Java class library.
====
.Source object
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/Source.java[tag=documentation]
----
====
.Target object
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/Target.java[tag=documentation]
----
====
.Mapper definition
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/ErroneousCustomExclusionMapper.java[tag=documentation]
----
====
We want to exclude the `NestedTarget` from the automatic sub-mapping method generation.
.CustomMappingExclusionProvider
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/nestedbeans/exclusions/custom/CustomMappingExclusionProvider.java[tag=documentation]
----
====
To use a custom SPI implementation, it must be located in a separate JAR file
together with the file `META-INF/services/org.mapstruct.ap.spi.MappingExclusionProvider` with the fully qualified name of your custom implementation as content
(e.g. `org.mapstruct.example.CustomMappingExclusionProvider`).
This JAR file needs to be added to the annotation processor classpath
(i.e. add it next to the place where you added the mapstruct-processor jar).
[[custom-builder-provider]]
=== Custom Builder Provider
MapStruct offers the possibility to override the `DefaultProvider` via the Service Provider Interface (SPI).
A nice example is to provide support for a custom builder strategy.
.Custom Builder Provider which disables Builder support
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-main}/spi/NoOpBuilderProvider.java[tag=documentation]
----
====
[[custom-enum-naming-strategy]]
=== Custom Enum Naming Strategy
MapStruct offers the possibility to override the `EnumMappingStrategy` via the Service Provider Interface (SPI).
This can be used when you have certain enums that follow some conventions within your organization.
For example all enums which implement an interface named `CustomEnumMarker` are prefixed with `CUSTOM_`
and the default value for them when mapping from `null` is `UNSPECIFIED`
.Normal Enum
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CheeseType {
BRIE,
ROQUEFORT;
}
----
====
.Custom marker enum
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public enum CustomCheeseType implements CustomEnumMarker {
UNSPECIFIED,
CUSTOM_BRIE,
CUSTOM_ROQUEFORT;
}
----
====
We want `CheeseType` and `CustomCheeseType` to be mapped without the need to manually define the value mappings:
.Custom enum mapping
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CheeseTypeMapper {
CheeseType map(CustomCheeseType cheese);
CustomCheeseType map(CheeseType cheese);
}
----
====
This can be achieved with implementing the SPI `org.mapstruct.ap.spi.EnumMappingStrategy` as in the following example.
Heres an implemented `org.mapstruct.ap.spi.EnumMappingStrategy`:
.Custom enum naming strategy
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class CustomEnumMappingStrategy extends DefaultEnumMappingStrategy {
@Override
public String getDefaultNullEnumConstant(TypeElement enumType) {
if ( isCustomEnum( enumType ) ) {
return "UNSPECIFIED";
}
return super.getDefaultNullEnumConstant( enumType );
}
@Override
public String getEnumConstant(TypeElement enumType, String enumConstant) {
if ( isCustomEnum( enumType ) ) {
return getCustomEnumConstant( enumConstant );
}
return super.getEnumConstant( enumType, enumConstant );
}
protected String getCustomEnumConstant(String enumConstant) {
if ( "UNSPECIFIED".equals( enumConstant ) ) {
return MappingConstantsGem.NULL;
}
return enumConstant.replace( "CUSTOM_", "" );
}
protected boolean isCustomEnum(TypeElement enumType) {
for ( TypeMirror enumTypeInterface : enumType.getInterfaces() ) {
if ( typeUtils.asElement( enumTypeInterface ).getSimpleName().contentEquals( "CustomEnumMarker" ) ) {
return true;
}
}
return false;
}
}
----
====
The generated code then for the `CheeseMapper` looks like:
.Generated CheeseTypeMapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class CheeseTypeMapperImpl implements CheeseTypeMapper {
@Override
public CheeseType map(CustomCheeseType cheese) {
if ( cheese == null ) {
return null;
}
CheeseType cheeseType;
switch ( cheese ) {
case UNRECOGNIZED: cheeseType = null;
break;
case CUSTOM_BRIE: cheeseType = CheeseType.BRIE;
break;
case CUSTOM_ROQUEFORT: cheeseType = CheeseType.ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return cheeseType;
}
@Override
public CustomCheeseType map(CheeseType cheese) {
if ( cheese == null ) {
return CustomCheeseType.UNSPECIFIED;
}
CustomCheeseType customCheeseType;
switch ( cheese ) {
case BRIE: customCheeseType = CustomCheeseType.CUSTOM_BRIE;
break;
case ROQUEFORT: customCheeseType = CustomCheeseType.CUSTOM_ROQUEFORT;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + cheese );
}
return customCheeseType;
}
}
----
====
[[custom-enum-transformation-strategy]]
=== Custom Enum Transformation Strategy
MapStruct offers the possibility to other transformations strategies by implementing `EnumTransformationStrategy` via the Service Provider Interface (SPI).
A nice example is to provide support for a custom transformation strategy.
.Custom Enum Transformation Strategy which lower-cases the value and applies a suffix
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationStrategy.java[tag=documentation]
----
====