Skip to content

Commit

Permalink
Allow user customized MappingStrategies
Browse files Browse the repository at this point in the history
Break out the mapping strategy construction functionality to allow users to insert their own strategies.
  • Loading branch information
Dan Jasek committed Sep 22, 2016
1 parent 465c383 commit 300e914
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 114 deletions.
6 changes: 6 additions & 0 deletions core/src/main/java/ma/glasnost/orika/MapperFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package ma.glasnost.orika;

import java.util.List;
import java.util.Set;

import ma.glasnost.orika.converter.ConverterFactory;
Expand Down Expand Up @@ -408,6 +409,11 @@ <S, D> Type<? extends D> lookupConcreteDestinationType(Type<S> sourceType, Type<
* @return the {@link UnenhanceStrategy} associated with this MapperFactory.
*/
UnenhanceStrategy getUserUnenhanceStrategy();

/**
* @return the list of factories used to generate {@link MappingStrategy}
*/
List<MappingStrategyFactory> getMappingStrategyFactories();


/**
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/java/ma/glasnost/orika/MappingStrategyFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ma.glasnost.orika;

import ma.glasnost.orika.impl.mapping.strategy.MappingStrategyRecorder;

public interface MappingStrategyFactory {
/**
* Identifies if a mapping strategy can be applied to a scenario with the given context.
*/
boolean isApplicable(MappingStrategyRecorder data);

/**
* Builds the mapping strategy to be applied to the given context.
*/
MappingStrategy build(MappingStrategyRecorder data);
}
59 changes: 44 additions & 15 deletions core/src/main/java/ma/glasnost/orika/impl/DefaultMapperFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

import ma.glasnost.orika.BoundMapperFacade;
import ma.glasnost.orika.Converter;
import ma.glasnost.orika.DefaultFieldMapper;
import ma.glasnost.orika.Filter;
import ma.glasnost.orika.MapEntry;
import ma.glasnost.orika.MappedTypePair;
import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.MappingContextFactory;
import ma.glasnost.orika.MappingException;
import ma.glasnost.orika.ObjectFactory;
import ma.glasnost.orika.Properties;
import com.google.common.collect.ImmutableList;
import ma.glasnost.orika.*;
import ma.glasnost.orika.StateReporter.Reportable;
import ma.glasnost.orika.constructor.ConstructorResolverStrategy;
import ma.glasnost.orika.converter.ConverterFactory;
Expand All @@ -69,6 +57,10 @@
import ma.glasnost.orika.impl.generator.CompilerStrategy.SourceCodeGenerationException;
import ma.glasnost.orika.impl.generator.MapperGenerator;
import ma.glasnost.orika.impl.generator.ObjectFactoryGenerator;
import ma.glasnost.orika.impl.mapping.strategy.CopyByReferenceStrategy.CopyByReferenceStrategyFactory;
import ma.glasnost.orika.impl.mapping.strategy.InstantiateAndUseCustomMapperStrategy.InstantiateAndUseCustomMapperStrategyFactory;
import ma.glasnost.orika.impl.mapping.strategy.MapExistingAndUseCustomMapperStrategy.MapExistingAndUseCustomMapperStrategyFactory;
import ma.glasnost.orika.impl.mapping.strategy.UseConverterStrategy.UseConverterStrategyFactory;
import ma.glasnost.orika.impl.util.ClassUtil;
import ma.glasnost.orika.inheritance.DefaultSuperTypeResolverStrategy;
import ma.glasnost.orika.inheritance.SuperTypeResolverStrategy;
Expand Down Expand Up @@ -126,6 +118,7 @@ public class DefaultMapperFactory implements MapperFactory, Reportable {
protected final ClassMapBuilderFactory classMapBuilderFactory;
protected ClassMapBuilderFactory chainClassMapBuilderFactory;
protected final Map<MapperKey, Set<ClassMap<Object, Object>>> usedMapperMetadataRegistry;
protected final List<MappingStrategyFactory> mappingStrategyFactories;

protected final boolean useAutoMapping;
protected final boolean useBuiltinConverters;
Expand Down Expand Up @@ -157,6 +150,7 @@ protected DefaultMapperFactory(MapperFactoryBuilder<?, ?> builder) {
this.contextFactory = builder.mappingContextFactory;
this.nonCyclicContextFactory = new NonCyclicMappingContext.Factory(this.contextFactory.getGlobalProperties());
this.exceptionUtil = new ExceptionUtility(this, builder.dumpStateOnException);
this.mappingStrategyFactories = buildMappingStrategyFactories(builder.mappingStrategyFactories);
this.mapperFacade = buildMapperFacade(contextFactory, unenhanceStrategy);
this.concreteTypeRegistry = new ConcurrentHashMap<java.lang.reflect.Type, Type<?>>();

Expand Down Expand Up @@ -191,7 +185,7 @@ protected DefaultMapperFactory(MapperFactoryBuilder<?, ?> builder) {
props.put(Properties.MAPPER_FACTORY, this);
props.put(Properties.FILTERS, this.filtersRegistry);
props.put(Properties.CAPTURE_FIELD_CONTEXT, builder.captureFieldContext);

/*
* Register default concrete types for common collection types; these
* can be overridden as needed by user code.
Expand Down Expand Up @@ -300,6 +294,11 @@ public static abstract class MapperFactoryBuilder<F extends DefaultMapperFactory
* upon mapping of every field.
*/
protected Boolean captureFieldContext;
/**
* User defined factories to use when building mapping strategies.
* These user defined factories will take precedence over the default strategy factories.
*/
protected List<MappingStrategyFactory> mappingStrategyFactories;

/**
* Instantiates a new MapperFactoryBuilder
Expand All @@ -319,6 +318,7 @@ public MapperFactoryBuilder() {
favorExtension = valueOf(getProperty(FAVOR_EXTENSION, "false"));
captureFieldContext = valueOf(getProperty(CAPTURE_FIELD_CONTEXT, "false"));
codeGenerationStrategy = new DefaultCodeGenerationStrategy();
mappingStrategyFactories = new ArrayList<MappingStrategyFactory>();
}

/**
Expand Down Expand Up @@ -557,6 +557,11 @@ public B codeGenerationStrategy(CodeGenerationStrategy codeGenerationStrategy) {
this.codeGenerationStrategy = codeGenerationStrategy ;
return self();
}

public B registerMappingStrategy(MappingStrategyFactory factory) {
this.mappingStrategyFactories.add(factory);
return self();
}

/**
* @return a new instance of the Factory for which this builder is
Expand Down Expand Up @@ -678,6 +683,21 @@ public boolean isAcceptable(Type<?> type) {

return unenhancer;
}

protected List<MappingStrategyFactory> buildMappingStrategyFactories(List<MappingStrategyFactory> userFactories) {
ImmutableList.Builder<MappingStrategyFactory> mappingStrategyFactories = ImmutableList.builder();

// Order is important here. The first factory in the list that can build a strategy is the one we will get.
// Add the user defined ones first so that they override the defaults.
mappingStrategyFactories.addAll(userFactories);
// The default factories.
mappingStrategyFactories.add(new CopyByReferenceStrategyFactory());
mappingStrategyFactories.add(new UseConverterStrategyFactory());
mappingStrategyFactories.add(new InstantiateAndUseCustomMapperStrategyFactory());
mappingStrategyFactories.add(new MapExistingAndUseCustomMapperStrategyFactory());

return mappingStrategyFactories.build();
}

/**
* Builds the MapperFacade for this factory. Subclasses can override this
Expand Down Expand Up @@ -1535,6 +1555,15 @@ public <A, B> BoundMapperFacade<A, B> getMapperFacade(Class<A> aType, Class<B> b
public UnenhanceStrategy getUserUnenhanceStrategy() {
return userUnenahanceStrategy;
}

/*
* (non-Javadoc)
*
* @see ma.glasnost.orika.MapperFactory#getMappingStrategyFactories()
*/
public List<MappingStrategyFactory> getMappingStrategyFactories() {
return mappingStrategyFactories;
}

/*
* (non-Javadoc)
Expand Down
133 changes: 72 additions & 61 deletions core/src/main/java/ma/glasnost/orika/impl/MapperFacadeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,8 @@
import java.util.Map.Entry;
import java.util.Set;

import ma.glasnost.orika.Converter;
import ma.glasnost.orika.MapEntry;
import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.MappingContextFactory;
import ma.glasnost.orika.MappingException;
import ma.glasnost.orika.MappingStrategy;
import ma.glasnost.orika.*;
import ma.glasnost.orika.MappingStrategy.Key;
import ma.glasnost.orika.ObjectFactory;
import ma.glasnost.orika.StateReporter.Reportable;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.mapping.strategy.MappingStrategyRecorder;
Expand Down Expand Up @@ -78,7 +69,7 @@ public class MapperFacadeImpl implements MapperFacade, Reportable {
* @param unenhanceStrategy
*/
public MapperFacadeImpl(final MapperFactory mapperFactory, final MappingContextFactory contextFactory,
final UnenhanceStrategy unenhanceStrategy, final ExceptionUtility exceptionUtil) {
final UnenhanceStrategy unenhanceStrategy, final ExceptionUtility exceptionUtil) {
this.mapperFactory = mapperFactory;
this.exceptionUtil = exceptionUtil;
this.unenhanceStrategy = unenhanceStrategy;
Expand Down Expand Up @@ -164,60 +155,14 @@ protected Class<?> getClass(final Object object) {
* @return a MappingStrategy suitable to map the source and destination
* object
*/
public <S, D> MappingStrategy resolveMappingStrategy(final S sourceObject, final java.lang.reflect.Type initialSourceType,
public <S,D> MappingStrategy resolveMappingStrategy(final S sourceObject, final java.lang.reflect.Type initialSourceType,
final java.lang.reflect.Type initialDestinationType, final boolean mapInPlace, final MappingContext context) {

Key key = new Key(getClass(sourceObject), initialSourceType, initialDestinationType, mapInPlace);
MappingStrategy strategy = strategyCache.get(key);

if (strategy == null) {

@SuppressWarnings("unchecked")
Type<S> sourceType = (Type<S>) (initialSourceType != null ? TypeFactory.valueOf(initialSourceType)
: TypeFactory.typeOf(sourceObject));
Type<D> destinationType = TypeFactory.valueOf(initialDestinationType);

MappingStrategyRecorder strategyRecorder = new MappingStrategyRecorder(key, unenhanceStrategy);

final Type<S> resolvedSourceType = normalizeSourceType(sourceObject, sourceType, destinationType);

strategyRecorder.setResolvedSourceType(resolvedSourceType);
strategyRecorder.setResolvedDestinationType(destinationType);

if (!mapInPlace && canCopyByReference(destinationType, resolvedSourceType)) {
/*
* We can copy by reference when destination is assignable from
* source and the source is immutable
*/
strategyRecorder.setCopyByReference(true);
} else if (!mapInPlace && canConvert(resolvedSourceType, destinationType)) {
strategyRecorder.setResolvedConverter(mapperFactory.getConverterFactory().getConverter(resolvedSourceType, destinationType));

} else {
strategyRecorder.setInstantiate(true);
Type<? extends D> resolvedDestinationType = mapperFactory.lookupConcreteDestinationType(resolvedSourceType, destinationType, context);
if (resolvedDestinationType == null) {
if (!ClassUtil.isConcrete(destinationType)) {
MappingException e = new MappingException("No concrete class mapping defined for source class "
+ resolvedSourceType.getName());
e.setDestinationType(destinationType);
e.setSourceType(resolvedSourceType);
throw exceptionUtil.decorate(e);
} else {
resolvedDestinationType = destinationType;
}
}

strategyRecorder.setResolvedDestinationType(resolvedDestinationType);
strategyRecorder.setResolvedMapper(resolveMapper(resolvedSourceType, resolvedDestinationType));
if (!mapInPlace) {
strategyRecorder.setResolvedObjectFactory(mapperFactory.lookupObjectFactory(resolvedDestinationType, resolvedSourceType));
}
}
strategy = strategyRecorder.playback();
if (log.isDebugEnabled()) {
log.debug(strategyRecorder.describeDetails());
}
strategy = buildMappingStrategy(sourceObject, initialSourceType, initialDestinationType,
mapInPlace, context, key);
MappingStrategy existing = strategyCache.putIfAbsent(key, strategy);
if (existing != null) {
strategy = existing;
Expand All @@ -234,7 +179,73 @@ public <S, D> MappingStrategy resolveMappingStrategy(final S sourceObject, final

return strategy;
}


private <S, D> MappingStrategy buildMappingStrategy(S sourceObject,
java.lang.reflect.Type initialSourceType,
java.lang.reflect.Type initialDestinationType,
boolean mapInPlace,
MappingContext context,
Key key) {
@SuppressWarnings("unchecked")
Type<S> sourceType = (Type<S>) (initialSourceType != null ? TypeFactory.valueOf(initialSourceType)
: TypeFactory.typeOf(sourceObject));
Type<D> destinationType = TypeFactory.valueOf(initialDestinationType);

MappingStrategyRecorder strategyRecorder = new MappingStrategyRecorder(key, unenhanceStrategy);

final Type<S> resolvedSourceType = normalizeSourceType(sourceObject, sourceType, destinationType);

strategyRecorder.setResolvedSourceType(resolvedSourceType);
strategyRecorder.setResolvedDestinationType(destinationType);

if (!mapInPlace && canCopyByReference(destinationType, resolvedSourceType)) {
/*
* We can copy by reference when destination is assignable from
* source and the source is immutable
*/
strategyRecorder.setCopyByReference(true);
} else if (!mapInPlace && canConvert(resolvedSourceType, destinationType)) {
strategyRecorder.setResolvedConverter(mapperFactory.getConverterFactory().getConverter(resolvedSourceType, destinationType));

} else {
strategyRecorder.setInstantiate(true);
Type<? extends D> resolvedDestinationType = mapperFactory.lookupConcreteDestinationType(resolvedSourceType,
destinationType,
context);
if (resolvedDestinationType == null) {
if (!ClassUtil.isConcrete(destinationType)) {
MappingException e = new MappingException("No concrete class mapping defined for source class "
+ resolvedSourceType.getName());
e.setDestinationType(destinationType);
e.setSourceType(resolvedSourceType);
throw exceptionUtil.decorate(e);
} else {
resolvedDestinationType = destinationType;
}
}

strategyRecorder.setResolvedDestinationType(resolvedDestinationType);
strategyRecorder.setResolvedMapper(resolveMapper(resolvedSourceType, resolvedDestinationType));
if (!mapInPlace) {
strategyRecorder.setResolvedObjectFactory(
mapperFactory.lookupObjectFactory(resolvedDestinationType, resolvedSourceType));
}
}
for(MappingStrategyFactory strategyFactory : mapperFactory.getMappingStrategyFactories()) {
if(strategyFactory.isApplicable(strategyRecorder)) {
MappingStrategy resolvedStrategy = strategyFactory.build(strategyRecorder);
if (log.isDebugEnabled()) {
log.debug(strategyRecorder.describeDetails(resolvedStrategy));
}
return resolvedStrategy;
}
}
if (log.isDebugEnabled()) {
log.debug(strategyRecorder.describeDetails(null));
}
throw new MappingException("No mapping strategy found to satisfy requirements.");
}

public <S, D> D map(final S sourceObject, final Type<S> sourceType, final Type<D> destinationType, final MappingContext context) {
return map(sourceObject, sourceType, destinationType, context, null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.MappingStrategy;
import ma.glasnost.orika.MappingStrategyFactory;
import ma.glasnost.orika.metadata.Type;

/**
Expand Down Expand Up @@ -69,4 +70,15 @@ public Type<Object> getAType() {
public Type<Object> getBType() {
return null;
}

public static class CopyByReferenceStrategyFactory implements MappingStrategyFactory {

public boolean isApplicable(MappingStrategyRecorder data) {
return data.isCopyByReference();
}

public MappingStrategy build(MappingStrategyRecorder data) {
return CopyByReferenceStrategy.getInstance();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@

import java.util.Map;

import ma.glasnost.orika.Mapper;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.ObjectFactory;
import ma.glasnost.orika.*;
import ma.glasnost.orika.impl.ReversedMapper;
import ma.glasnost.orika.metadata.Type;
import ma.glasnost.orika.unenhance.UnenhanceStrategy;

Expand Down Expand Up @@ -60,4 +59,23 @@ protected void describeMembers(Map<String, Object> members) {
super.describeMembers(members);
members.put("objectFactory", objectFactory);
}

public static class InstantiateAndUseCustomMapperStrategyFactory implements MappingStrategyFactory {

public boolean isApplicable(MappingStrategyRecorder data) {
return data.getResolvedObjectFactory() != null;
}

public MappingStrategy build(MappingStrategyRecorder data) {
Mapper<Object, Object> resolvedMapper = data.getResolvedMapper();
if(data.isMapReverse()) {
resolvedMapper = ReversedMapper.reverse(resolvedMapper);
}
return new InstantiateAndUseCustomMapperStrategy((Type<Object>) data.getResolvedSourceType(),
(Type<Object>) data.getResolvedDestinationType(),
resolvedMapper,
data.getResolvedObjectFactory(),
data.getUnenhanceStrategy());
}
}
}
Loading

0 comments on commit 300e914

Please sign in to comment.