@ -1,5 +1,5 @@
@@ -1,5 +1,5 @@
/ *
* Copyright 2011 - 2013 the original author or authors .
* Copyright 2011 - 2014 the original author or authors .
*
* Licensed under the Apache License , Version 2 . 0 ( the "License" ) ;
* you may not use this file except in compliance with the License .
@ -17,13 +17,13 @@ package org.springframework.data.mongodb.core.convert;
@@ -17,13 +17,13 @@ package org.springframework.data.mongodb.core.convert;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.HashMap ;
import java.util.HashSet ;
import java.util.LinkedHashSet ;
import java.util.List ;
import java.util.Locale ;
import java.util.Map ;
import java.util.Set ;
import java.util.concurrent.ConcurrentHashMap ;
import java.util.concurrent.ConcurrentMap ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
@ -56,6 +56,7 @@ import org.springframework.util.Assert;
@@ -56,6 +56,7 @@ import org.springframework.util.Assert;
* .
*
* @author Oliver Gierke
* @author Thomas Darimont
* /
public class CustomConversions {
@ -67,7 +68,7 @@ public class CustomConversions {
@@ -67,7 +68,7 @@ public class CustomConversions {
private final Set < ConvertiblePair > writingPairs ;
private final Set < Class < ? > > customSimpleTypes ;
private final SimpleTypeHolder simpleTypeHolder ;
private final Map < Class < ? > , HashMap < Class < ? > , CacheValue > > cache ;
private final ConcurrentMap < ConvertiblePair , CacheValue > customReadTargetTypes ;
private final List < Object > converters ;
@ -90,7 +91,7 @@ public class CustomConversions {
@@ -90,7 +91,7 @@ public class CustomConversions {
this . readingPairs = new LinkedHashSet < ConvertiblePair > ( ) ;
this . writingPairs = new LinkedHashSet < ConvertiblePair > ( ) ;
this . customSimpleTypes = new HashSet < Class < ? > > ( ) ;
this . cache = new HashMap < Class < ? > , HashMap < Class < ? > , CacheValue > > ( ) ;
this . customReadTargetTypes = new Concurrent HashMap< GenericConverter . ConvertiblePair , CacheValue > ( ) ;
this . converters = new ArrayList < Object > ( ) ;
this . converters . addAll ( converters ) ;
@ -195,25 +196,25 @@ public class CustomConversions {
@@ -195,25 +196,25 @@ public class CustomConversions {
*
* @param pair
* /
private void register ( ConverterRegistration context ) {
private void register ( ConverterRegistration converterRegistration ) {
ConvertiblePair pair = context . getConvertiblePair ( ) ;
ConvertiblePair pair = converterRegistration . getConvertiblePair ( ) ;
if ( context . isReading ( ) ) {
if ( converterRegistration . isReading ( ) ) {
readingPairs . add ( pair ) ;
if ( LOG . isWarnEnabled ( ) & & ! context . isSimpleSourceType ( ) ) {
if ( LOG . isWarnEnabled ( ) & & ! converterRegistration . isSimpleSourceType ( ) ) {
LOG . warn ( String . format ( READ_CONVERTER_NOT_SIMPLE , pair . getSourceType ( ) , pair . getTargetType ( ) ) ) ;
}
}
if ( context . isWriting ( ) ) {
if ( converterRegistration . isWriting ( ) ) {
writingPairs . add ( pair ) ;
customSimpleTypes . add ( pair . getSourceType ( ) ) ;
if ( LOG . isWarnEnabled ( ) & & ! context . isSimpleTargetType ( ) ) {
if ( LOG . isWarnEnabled ( ) & & ! converterRegistration . isSimpleTargetType ( ) ) {
LOG . warn ( String . format ( WRITE_CONVERTER_NOT_SIMPLE , pair . getSourceType ( ) , pair . getTargetType ( ) ) ) ;
}
}
@ -223,11 +224,11 @@ public class CustomConversions {
@@ -223,11 +224,11 @@ public class CustomConversions {
* Returns the target type to convert to in case we have a custom conversion registered to convert the given source
* type into a Mongo native one .
*
* @param source must not be { @literal null }
* @param sourceType must not be { @literal null }
* @return
* /
public Class < ? > getCustomWriteTarget ( Class < ? > source ) {
return getCustomWriteTarget ( source , null ) ;
public Class < ? > getCustomWriteTarget ( Class < ? > sourceType ) {
return getCustomWriteTarget ( sourceType , null ) ;
}
/ * *
@ -235,72 +236,78 @@ public class CustomConversions {
@@ -235,72 +236,78 @@ public class CustomConversions {
* oth the given expected type though . If { @code expectedTargetType } is { @literal null } we will simply return the
* first target type matching or { @literal null } if no conversion can be found .
*
* @param source must not be { @literal null }
* @param expec tedTargetType
* @param sourceType must not be { @literal null }
* @param reques tedTargetType
* @return
* /
public Class < ? > getCustomWriteTarget ( Class < ? > source , Class < ? > expec tedTargetType) {
public Class < ? > getCustomWriteTarget ( Class < ? > sourceType , Class < ? > reques tedTargetType) {
Assert . notNull ( source ) ;
return getCustomTarget ( source , expectedTargetType , writingPairs ) ;
Assert . notNull ( sourceType ) ;
return getCustomTarget ( sourceType , requestedTargetType , writingPairs ) ;
}
/ * *
* Returns whether we have a custom conversion registered to write into a Mongo native type . The returned type might
* be a subclass oth the given expected type though .
* be a subclass of the given expected type though .
*
* @param source must not be { @literal null }
* @param sourceType must not be { @literal null }
* @return
* /
public boolean hasCustomWriteTarget ( Class < ? > source ) {
return hasCustomWriteTarget ( source , null ) ;
public boolean hasCustomWriteTarget ( Class < ? > sourceType ) {
Assert . notNull ( sourceType ) ;
return hasCustomWriteTarget ( sourceType , null ) ;
}
/ * *
* Returns whether we have a custom conversion registered to write an object of the given source type into an object
* of the given Mongo native target type .
*
* @param source must not be { @literal null } .
* @param expec tedTargetType
* @param sourceType must not be { @literal null } .
* @param reques tedTargetType
* @return
* /
public boolean hasCustomWriteTarget ( Class < ? > source , Class < ? > expectedTargetType ) {
return getCustomWriteTarget ( source , expectedTargetType ) ! = null ;
public boolean hasCustomWriteTarget ( Class < ? > sourceType , Class < ? > requestedTargetType ) {
Assert . notNull ( sourceType ) ;
return getCustomWriteTarget ( sourceType , requestedTargetType ) ! = null ;
}
/ * *
* Returns whether we have a custom conversion registered to read the given source into the given target type .
*
* @param source must not be { @literal null }
* @param expec tedTargetType must not be { @literal null }
* @param sourceType must not be { @literal null }
* @param reques tedTargetType must not be { @literal null }
* @return
* /
public boolean hasCustomReadTarget ( Class < ? > source , Class < ? > expec tedTargetType) {
public boolean hasCustomReadTarget ( Class < ? > sourceType , Class < ? > reques tedTargetType) {
Assert . notNull ( source ) ;
Assert . notNull ( expec tedTargetType) ;
Assert . notNull ( sourceType ) ;
Assert . notNull ( reques tedTargetType) ;
return getCustomReadTarget ( source , expec tedTargetType) ! = null ;
return getCustomReadTarget ( sourceType , reques tedTargetType ) ! = null ;
}
/ * *
* Inspects the given { @link ConvertiblePair } for ones that have a source compatible type as source . Additionally
* checks assignabilty of the target type if one is given .
* checks assignabili ty of the target type if one is given .
*
* @param source must not be { @literal null }
* @param expectedTargetType
* @param pairs must not be { @literal null }
* @param sourceType must not be { @literal null } .
* @param requestedTargetType can be { @literal null } .
* @param pairs must not be { @literal null } .
* @return
* /
private static Class < ? > getCustomTarget ( Class < ? > source , Class < ? > expectedTargetType , Iterable < ConvertiblePair > pairs ) {
private static Class < ? > getCustomTarget ( Class < ? > sourceType , Class < ? > requestedTargetType ,
Iterable < ConvertiblePair > pairs ) {
Assert . notNull ( source ) ;
Assert . notNull ( sourceType ) ;
Assert . notNull ( pairs ) ;
for ( ConvertiblePair typePair : pairs ) {
if ( typePair . getSourceType ( ) . isAssignableFrom ( source ) ) {
if ( typePair . getSourceType ( ) . isAssignableFrom ( sourceType ) ) {
Class < ? > targetType = typePair . getTargetType ( ) ;
if ( expec tedTargetType = = null | | targetType . isAssignableFrom ( expec tedTargetType) ) {
if ( reques tedTargetType = = null | | targetType . isAssignableFrom ( reques tedTargetType) ) {
return targetType ;
}
}
@ -309,27 +316,33 @@ public class CustomConversions {
@@ -309,27 +316,33 @@ public class CustomConversions {
return null ;
}
private Class < ? > getCustomReadTarget ( Class < ? > source , Class < ? > expectedTargetType ) {
Class < ? > type = expectedTargetType = = null ? PlaceholderType . class : expectedTargetType ;
Map < Class < ? > , CacheValue > map ;
CacheValue toReturn ;
/ * *
* Returns the actual target type for the given { @code sourceType } and { @code requestedTargetType } . Note that the
* returned { @link Class } could be an assignable type to the given { @code requestedTargetType } .
*
* @param sourceType must not be { @literal null } .
* @param requestedTargetType can be { @literal null } .
* @return
* /
private Class < ? > getCustomReadTarget ( Class < ? > sourceType , Class < ? > requestedTargetType ) {
if ( ( map = cache . get ( source ) ) = = null | | ( toReturn = map . get ( type ) ) = = null ) {
Assert . notNull ( sourceType ) ;
Class < ? > target = getCustomTarget ( source , type , readingPairs ) ;
if ( requestedTargetType = = null ) {
return null ;
}
if ( cache . get ( source ) = = null ) {
cache . put ( source , new HashMap < Class < ? > , CacheValue > ( ) ) ;
}
ConvertiblePair lookupKey = new ConvertiblePair ( sourceType , requestedTargetType ) ;
CacheValue readTargetTypeValue = customReadTargetTypes . get ( lookupKey ) ;
Map < Class < ? > , CacheValue > value = cache . get ( source ) ;
toReturn = target = = null ? CacheValue . NULL : new CacheValue ( target ) ;
value . put ( type , toReturn ) ;
if ( readTargetTypeValue ! = null ) {
return readTargetTypeValue . getType ( ) ;
}
return toReturn . clazz ;
readTargetTypeValue = CacheValue . of ( getCustomTarget ( sourceType , requestedTargetType , readingPairs ) ) ;
CacheValue cacheValue = customReadTargetTypes . putIfAbsent ( lookupKey , readTargetTypeValue ) ;
return cacheValue ! = null ? cacheValue . getType ( ) : readTargetTypeValue . getType ( ) ;
}
@WritingConverter
@ -338,8 +351,10 @@ public class CustomConversions {
@@ -338,8 +351,10 @@ public class CustomConversions {
INSTANCE ;
public Set < ConvertiblePair > getConvertibleTypes ( ) {
ConvertiblePair localeToString = new ConvertiblePair ( Locale . class , String . class ) ;
ConvertiblePair booleanToString = new ConvertiblePair ( Character . class , String . class ) ;
return new HashSet < ConvertiblePair > ( Arrays . asList ( localeToString , booleanToString ) ) ;
}
@ -348,29 +363,29 @@ public class CustomConversions {
@@ -348,29 +363,29 @@ public class CustomConversions {
}
}
/ * *
* Placeholder type to allow registering not - found values in the converter cache .
*
* @author Patryk Wasik
* @author Oliver Gierke
* /
private static class PlaceholderType {
}
/ * *
* Wrapper to safely store { @literal null } values in the type cache .
*
* @author Patryk Wasik
* @author Oliver Gierke
* @author Thomas Darimont
* /
private static class CacheValue {
public static final CacheValue NULL = new CacheValue ( null ) ;
private final Class < ? > clazz ;
private static final CacheValue ABSENT = new CacheValue ( null ) ;
private final Class < ? > type ;
public CacheValue ( Class < ? > type ) {
this . type = type ;
}
public Class < ? > getType ( ) {
return type ;
}
public CacheValue ( Class < ? > clazz ) {
this . clazz = clazz ;
static CacheValue of ( Class < ? > type ) {
return type = = null ? ABSENT : new CacheValue ( type ) ;
}
}
}