diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConfigurableTypeMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConfigurableTypeMapper.java new file mode 100644 index 000000000..4a914a5cc --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/ConfigurableTypeMapper.java @@ -0,0 +1,98 @@ +/* + * Copyright 2011 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. + * 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.springframework.data.mongodb.core.convert; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; + +/** + * {@link TypeMapper} allowing to configure a {@link Map} containing {@link String} to {@link Class} mappings that will + * be used to map the values found under the configured type key (see {@link DefaultTypeMapper#setTypeKey(String)}. This + * allows declarative type mapping in a Spring config file for example. + * + * @author Oliver Gierke + */ +public class ConfigurableTypeMapper extends DefaultTypeMapper { + + private final Map, String> typeMap; + private boolean handleUnmappedClasses = false; + + /** + * Creates a new {@link ConfigurableTypeMapper} for the given type map. + * + * @param sourceTypeMap must not be {@literal null}. + */ + public ConfigurableTypeMapper(Map, String> sourceTypeMap) { + + Assert.notNull(sourceTypeMap); + + this.typeMap = new HashMap, String>(sourceTypeMap.size()); + + for (Entry, String> entry : sourceTypeMap.entrySet()) { + TypeInformation key = ClassTypeInformation.from(entry.getKey()); + String value = entry.getValue(); + + if (typeMap.containsValue(value)) { + throw new IllegalArgumentException(String.format( + "Detected mapping ambiguity! String %s cannot be mapped to more than one type!", value)); + } + + this.typeMap.put(key, value); + } + } + + /** + * Configures whether to try to handle unmapped classes by simply writing the class' name or loading the class as + * specified in the superclass. Defaults to {@literal false}. + * + * @param handleUnmappedClasses the handleUnmappedClasses to set + */ + public void setHandleUnmappedClasses(boolean handleUnmappedClasses) { + this.handleUnmappedClasses = handleUnmappedClasses; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.DefaultTypeMapper#getTypeInformation(java.lang.String) + */ + @Override + protected TypeInformation getTypeInformation(String value) { + + for (Entry, String> entry : typeMap.entrySet()) { + if (entry.getValue().equals(value)) { + return entry.getKey(); + } + } + + return handleUnmappedClasses ? super.getTypeInformation(value) : null; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.DefaultTypeMapper#getTypeString(org.springframework.data.util.TypeInformation) + */ + @Override + protected String getTypeString(TypeInformation typeInformation) { + + String key = typeMap.get(typeInformation); + return key != null ? key : handleUnmappedClasses ? super.getTypeString(typeInformation) : null; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultTypeMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultTypeMapper.java new file mode 100644 index 000000000..d2b640430 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/DefaultTypeMapper.java @@ -0,0 +1,147 @@ +/* + * Copyright 2011 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. + * 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.springframework.data.mongodb.core.convert; + +import java.util.List; + +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +import com.mongodb.BasicDBList; +import com.mongodb.DBObject; + +/** + * Default implementation of {@link TypeMapper} allowing configuration of the key to lookup and store type information + * in {@link DBObject}. The key defaults to {@link #DEFAULT_TYPE_KEY}. Actual type-to-{@link String} conversion and back + * is done in {@link #getTypeString(TypeInformation)} or {@link #getTypeInformation(String)} respectively. + * + * @author Oliver Gierke + */ +public class DefaultTypeMapper implements TypeMapper { + + public static final String DEFAULT_TYPE_KEY = "_class"; + @SuppressWarnings("rawtypes") + private static final TypeInformation LIST_TYPE_INFORMATION = ClassTypeInformation.from(List.class); + + private String typeKey = DEFAULT_TYPE_KEY; + + /** + * Sets the key to store the type information under. If set to {@literal null} no type information will be stored in + * the document. + * + * @param typeKey the typeKey to set + */ + public void setTypeKey(String typeKey) { + this.typeKey = typeKey; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.TypeMapper#isTypeKey(java.lang.String) + */ + public boolean isTypeKey(String key) { + return typeKey == null ? false : typeKey.equals(key); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.TypeMapper#readType(com.mongodb.DBObject) + */ + public TypeInformation readType(DBObject dbObject) { + + if (dbObject instanceof BasicDBList) { + return LIST_TYPE_INFORMATION; + } + + if (typeKey == null) { + return null; + } + + Object classToBeUsed = dbObject.get(typeKey); + + if (classToBeUsed == null) { + return null; + } + + return getTypeInformation(classToBeUsed.toString()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.TypeMapper#writeType(java.lang.Class, com.mongodb.DBObject) + */ + public void writeType(Class type, DBObject dbObject) { + writeType(ClassTypeInformation.from(type), dbObject); + + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.TypeMapper#writeType(java.lang.Class, com.mongodb.DBObject) + */ + public void writeType(TypeInformation info, DBObject dbObject) { + + Assert.notNull(info); + + if (typeKey == null) { + return; + } + + String string = getTypeString(info); + + if (string != null) { + dbObject.put(typeKey, getTypeString(info)); + } + } + + /** + * Turn the given type information into the String representation that shall be stored inside the {@link DBObject}. If + * the returned String is {@literal null} no type information will be stored. Default implementation simply returns + * the fully-qualified class name. + * + * @param typeInformation must not be {@literal null}. + * @return the String representation to be stored or {@literal null} if no type information shall be stored. + */ + protected String getTypeString(TypeInformation typeInformation) { + return typeInformation.getType().getName(); + } + + /** + * Returns the {@link TypeInformation} that shall be used when the given {@link String} value is found as type hint. + * The default implementation will simply interpret the given value as fully-qualified class name and try to load the + * class. Will return {@literal null} in case the given {@link String} is empty. Will not be called in case no + * {@link String} was found for the configured type key at all. + * + * @param value the type to load, must not be {@literal null}. + * @return the type to be used for the given {@link String} representation or {@literal null} if nothing found or the + * class cannot be loaded. + */ + protected TypeInformation getTypeInformation(String value) { + + if (!StringUtils.hasText(value)) { + return null; + } + + try { + return ClassTypeInformation.from(ClassUtils.forName(value, null)); + } catch (ClassNotFoundException e) { + return null; + } + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java index 36f062841..56491ff7c 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/MappingMongoConverter.java @@ -62,7 +62,6 @@ import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -73,9 +72,7 @@ import org.springframework.util.StringUtils; * @author Jon Brisbin * @author Oliver Gierke */ -public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware { - - public static final String CUSTOM_TYPE_KEY = "_class"; +public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware, TypeMapperProvider { @SuppressWarnings("rawtypes") private static final TypeInformation MAP_TYPE_INFORMATION = ClassTypeInformation.from(Map.class); @@ -89,6 +86,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App protected final MongoDbFactory mongoDbFactory; protected ApplicationContext applicationContext; protected boolean useFieldAccessOnly = true; + protected TypeMapper typeMapper = new DefaultTypeMapper(); /** * Creates a new {@link MappingMongoConverter} given the new {@link MongoDbFactory} and {@link MappingContext}. @@ -108,6 +106,25 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App this.mappingContext = mappingContext; } + /** + * Configures the {@link TypeMapper} to be used to add type information to {@link DBObject}s created by the converter + * and how to lookup type information from {@link DBObject}s when reading them. Uses a {@link DefaultTypeMapper} by + * default. Setting this to {@literal null} will reset the {@link TypeMapper} to the default one. + * + * @param typeMapper the typeMapper to set + */ + public void setTypeMapper(TypeMapper typeMapper) { + this.typeMapper = typeMapper == null ? new DefaultTypeMapper() : typeMapper; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.convert.MongoConverter#getTypeMapper() + */ + public TypeMapper getTypeMapper() { + return this.typeMapper; + } + /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.core.convert.MongoConverter#getMappingContext() @@ -126,7 +143,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App public void setUseFieldAccessOnly(boolean useFieldAccessOnly) { this.useFieldAccessOnly = useFieldAccessOnly; } - + /* * (non-Javadoc) * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) @@ -281,7 +298,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App boolean handledByCustomConverter = conversions.getCustomWriteTarget(obj.getClass(), DBObject.class) != null; if (!handledByCustomConverter) { - dbo.put(CUSTOM_TYPE_KEY, obj.getClass().getName()); + typeMapper.writeType(ClassTypeInformation.from(obj.getClass()), dbo); } writeInternal(obj, dbo); @@ -566,8 +583,8 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * the one given. This is usually the case if you store a subtype of the actual declared type of the property. * * @param type - * @param value - * @param dbObject + * @param value must not be {@literal null}. + * @param dbObject must not be {@literal null}. */ protected void addCustomTypeKeyIfNecessary(TypeInformation type, Object value, DBObject dbObject) { @@ -579,7 +596,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App boolean notTheSameClass = !value.getClass().equals(reference); if (notTheSameClass) { - dbObject.put(CUSTOM_TYPE_KEY, value.getClass().getName()); + typeMapper.writeType(value.getClass(), dbObject); } } @@ -711,11 +728,12 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App (BasicDBList) sourceValue); } - Class toType = findTypeToBeUsed((DBObject) sourceValue); + TypeInformation toType = findTypeToBeUsed((DBObject) sourceValue); // It's a complex object, have to read it in if (toType != null) { - dbo.removeField(CUSTOM_TYPE_KEY); + // TODO: why do we remove the type? + // dbo.removeField(CUSTOM_TYPE_KEY); o = read(toType, (DBObject) sourceValue); } else { o = read(mappingContext.getPersistentEntity(prop.getTypeInformation()), (DBObject) sourceValue); @@ -773,7 +791,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App Map sourceMap = dbObject.toMap(); for (Entry entry : sourceMap.entrySet()) { - if (entry.getKey().equals(CUSTOM_TYPE_KEY)) { + if (typeMapper.isTypeKey(entry.getKey())) { continue; } @@ -808,30 +826,15 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App * @return the type to be used for converting the given {@link DBObject} into or {@literal null} if there's no type * found. */ - protected Class findTypeToBeUsed(DBObject dbObject) { - - if (dbObject instanceof BasicDBList) { - return List.class; - } - - Object classToBeUsed = dbObject.get(CUSTOM_TYPE_KEY); - - if (classToBeUsed == null) { - return null; - } - - try { - return ClassUtils.forName(classToBeUsed.toString(), null); - } catch (ClassNotFoundException e) { - return null; - } + protected TypeInformation findTypeToBeUsed(DBObject dbObject) { + return typeMapper.readType(dbObject); } private Class getDefaultedTypeToBeUsed(DBObject dbObject) { - Class result = findTypeToBeUsed(dbObject); + TypeInformation result = findTypeToBeUsed(dbObject); if (result != null) { - return result; + return result.getType(); } return dbObject instanceof BasicDBList ? List.class : Map.class; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/TypeMapper.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/TypeMapper.java new file mode 100644 index 000000000..90723817b --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/TypeMapper.java @@ -0,0 +1,60 @@ +/* + * Copyright 2011 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. + * 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.springframework.data.mongodb.core.convert; + +import org.springframework.data.util.TypeInformation; + +import com.mongodb.DBObject; + +/** + * Interface to define strategies how to store type information in a {@link DBObject}. + * + * @author Oliver Gierke + */ +public interface TypeMapper { + + /** + * Returns whether the given key is the key being used as type key. + * + * @param key + * @return + */ + boolean isTypeKey(String key); + + /** + * Reads the {@link TypeInformation} from the given {@link DBObject}. + * + * @param dbObject must not be {@literal null}. + * @return + */ + TypeInformation readType(DBObject dbObject); + + /** + * Writes type information for the given type into the given {@link DBObject}. + * + * @param type must not be {@literal null}. + * @param dbObject must not be {@literal null}. + */ + void writeType(Class type, DBObject dbObject); + + /** + * Writes type information for the given {@link TypeInformation} into the given {@link DBObject}. + * + * @param type must not be {@literal null}. + * @param dbObject must not be {@literal null}. + */ + void writeType(TypeInformation type, DBObject dbObject); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/TypeMapperProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/TypeMapperProvider.java new file mode 100644 index 000000000..23a8130d8 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/TypeMapperProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2011 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. + * 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.springframework.data.mongodb.core.convert; + +/** + * Interfaces for components being able to provide a {@link TypeMapper}. + * + * @author Oliver Gierke + */ +public interface TypeMapperProvider { + + /** + * Returns the {@link TypeMapper}. + * + * @return the {@link TypeMapper} or {@literal null} if none available. + */ + TypeMapper getTypeMapper(); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ConvertingParameterAccessor.java index 0a7b5a009..0c4e00760 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/ConvertingParameterAccessor.java @@ -19,8 +19,9 @@ import java.util.Iterator; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoWriter; +import org.springframework.data.mongodb.core.convert.TypeMapper; +import org.springframework.data.mongodb.core.convert.TypeMapperProvider; import org.springframework.data.mongodb.core.geo.Distance; import org.springframework.data.repository.query.ParameterAccessor; @@ -34,7 +35,7 @@ import com.mongodb.DBObject; */ public class ConvertingParameterAccessor implements MongoParameterAccessor { - private final MongoWriter writer; + private final MongoWriter writer; private final MongoParameterAccessor delegate; /** @@ -42,7 +43,7 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { * * @param writer */ - public ConvertingParameterAccessor(MongoWriter writer, MongoParameterAccessor delegate) { + public ConvertingParameterAccessor(MongoWriter writer, MongoParameterAccessor delegate) { this.writer = writer; this.delegate = delegate; } @@ -98,7 +99,12 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { */ private Object getConvertedValue(Object value) { - return removeTypeInfoRecursively(writer.convertToMongoType(value)); + if (!(writer instanceof TypeMapperProvider)) { + return value; + } + + TypeMapper mapper = ((TypeMapperProvider) writer).getTypeMapper(); + return removeTypeInfoRecursively(writer.convertToMongoType(value), mapper); } /** @@ -107,26 +113,34 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { * @param object * @return */ - private Object removeTypeInfoRecursively(Object object) { + private Object removeTypeInfoRecursively(Object object, TypeMapper mapper) { - if (!(object instanceof DBObject)) { + if (!(object instanceof DBObject) || mapper == null) { return object; } DBObject dbObject = (DBObject) object; - - dbObject.removeField(MappingMongoConverter.CUSTOM_TYPE_KEY); + String keyToRemove = null; for (String key : dbObject.keySet()) { + + if (mapper.isTypeKey(key)) { + keyToRemove = key; + } + Object value = dbObject.get(key); if (value instanceof BasicDBList) { for (Object element : (BasicDBList) value) { - removeTypeInfoRecursively(element); + removeTypeInfoRecursively(element, mapper); } } else { - removeTypeInfoRecursively(value); + removeTypeInfoRecursively(value, mapper); } } + if (keyToRemove != null) { + dbObject.removeField(keyToRemove); + } + return dbObject; } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ConfigurableTypeMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ConfigurableTypeMapperUnitTests.java new file mode 100644 index 000000000..0ae5a2897 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/ConfigurableTypeMapperUnitTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2011 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. + * 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.springframework.data.mongodb.core.convert; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.util.TypeInformation; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Unit tests for {@link ConfigurableTypeMapper}. + * + * @author Oliver Gierke + */ +public class ConfigurableTypeMapperUnitTests { + + ConfigurableTypeMapper mapper; + + @Before + public void setUp() { + mapper = new ConfigurableTypeMapper(Collections.singletonMap(String.class, "1")); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsNullTypeMap() { + new ConfigurableTypeMapper(null); + } + + @Test(expected = IllegalArgumentException.class) + public void rejectsNonBijectionalMap() { + Map, String> map = new HashMap, String>(); + map.put(String.class, "1"); + map.put(Object.class, "1"); + + new ConfigurableTypeMapper(map); + } + + @Test + public void writesMapKeyForType() { + writesTypeToField(new BasicDBObject(), String.class, "1"); + writesTypeToField(new BasicDBObject(), Object.class, null); + } + + @Test + public void writesClassNamesForUnmappedValuesIfConfigured() { + mapper.setHandleUnmappedClasses(true); + writesTypeToField(new BasicDBObject(), String.class, "1"); + writesTypeToField(new BasicDBObject(), Object.class, Object.class.getName()); + } + + @Test + public void readsTypeForMapKey() { + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, "1"), String.class); + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, "unmapped"), null); + } + + @Test + public void readsTypeLoadingClassesForUnmappedTypesIfConfigured() { + mapper.setHandleUnmappedClasses(true); + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, "1"), String.class); + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, Object.class.getName()), Object.class); + } + + private void readsTypeFromField(DBObject dbObject, Class type) { + + TypeInformation typeInfo = mapper.readType(dbObject); + + if (type != null) { + assertThat(typeInfo, is(notNullValue())); + assertThat(typeInfo.getType(), is(typeCompatibleWith(type))); + } else { + assertThat(typeInfo, is(nullValue())); + } + } + + private void writesTypeToField(DBObject dbObject, Class type, Object value) { + + mapper.writeType(type, dbObject); + + if (value == null) { + assertThat(dbObject.keySet().isEmpty(), is(true)); + } else { + assertThat(dbObject.containsField(DefaultTypeMapper.DEFAULT_TYPE_KEY), is(true)); + assertThat(dbObject.get(DefaultTypeMapper.DEFAULT_TYPE_KEY), is(value)); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultTypeMapperUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultTypeMapperUnitTests.java new file mode 100644 index 000000000..5b9c6c5c7 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/DefaultTypeMapperUnitTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2011 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. + * 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.springframework.data.mongodb.core.convert; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.data.util.TypeInformation; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBObject; + +/** + * Unit tests for {@link DefaultTypeMapper}. + * + * @author Oliver Gierke + */ +public class DefaultTypeMapperUnitTests { + + DefaultTypeMapper mapper; + + @Before + public void setUp() { + mapper = new DefaultTypeMapper(); + } + + @Test + public void addsFullyQualifiedClassNameUnderDefaultKeyByDefault() { + writesTypeToField(DefaultTypeMapper.DEFAULT_TYPE_KEY, new BasicDBObject(), String.class); + } + + @Test + public void writesTypeToCustomFieldIfConfigured() { + mapper.setTypeKey("_custom"); + writesTypeToField("_custom", new BasicDBObject(), String.class); + } + + @Test + public void doesNotWriteTypeInformationInCaseKeyIsSetToNull() { + mapper.setTypeKey(null); + writesTypeToField(null, new BasicDBObject(), String.class); + } + + @Test + public void readsTypeFromDefaultKeyByDefault() { + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, String.class.getName()), String.class); + } + + @Test + public void readsTypeFromCustomFieldConfigured() { + mapper.setTypeKey("_custom"); + readsTypeFromField(new BasicDBObject("_custom", String.class.getName()), String.class); + } + + @Test + public void returnsListForBasicDBLists() { + readsTypeFromField(new BasicDBList(), List.class); + } + + @Test + public void returnsNullIfNoTypeInfoInDBObject() { + readsTypeFromField(new BasicDBObject(), null); + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, ""), null); + } + + @Test + public void returnsNullIfClassCannotBeLoaded() { + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, "fooBar"), null); + } + + @Test + public void returnsNullIfTypeKeySetToNull() { + mapper.setTypeKey(null); + readsTypeFromField(new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, String.class), null); + } + + @Test + public void returnsCorrectTypeKey() { + + assertThat(mapper.isTypeKey(DefaultTypeMapper.DEFAULT_TYPE_KEY), is(true)); + + mapper.setTypeKey("_custom"); + assertThat(mapper.isTypeKey("_custom"), is(true)); + assertThat(mapper.isTypeKey(DefaultTypeMapper.DEFAULT_TYPE_KEY), is(false)); + + mapper.setTypeKey(null); + assertThat(mapper.isTypeKey("_custom"), is(false)); + assertThat(mapper.isTypeKey(DefaultTypeMapper.DEFAULT_TYPE_KEY), is(false)); + } + + private void readsTypeFromField(DBObject dbObject, Class type) { + + TypeInformation typeInfo = mapper.readType(dbObject); + + if (type != null) { + assertThat(typeInfo, is(notNullValue())); + assertThat(typeInfo.getType(), is(typeCompatibleWith(type))); + } else { + assertThat(typeInfo, is(nullValue())); + } + } + + private void writesTypeToField(String field, DBObject dbObject, Class type) { + + mapper.writeType(type, dbObject); + + if (field == null) { + assertThat(dbObject.keySet().isEmpty(), is(true)); + } else { + assertThat(dbObject.containsField(field), is(true)); + assertThat(dbObject.get(field), is((Object) type.getName())); + } + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index 8d404919e..c578f4eff 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -153,7 +153,7 @@ public class MappingMongoConverterUnitTests { DBObject dbObject = new BasicDBObject(); dbObject.put("birthDate", new LocalDate()); - dbObject.put(MappingMongoConverter.CUSTOM_TYPE_KEY, Person.class.getName()); + dbObject.put(DefaultTypeMapper.DEFAULT_TYPE_KEY, Person.class.getName()); assertThat(converter.read(Contact.class, dbObject), is(Person.class)); } @@ -166,7 +166,7 @@ public class MappingMongoConverterUnitTests { DBObject dbObject = new BasicDBObject(); dbObject.put("birthDate", new LocalDate()); - dbObject.put(MappingMongoConverter.CUSTOM_TYPE_KEY, Person.class.getName()); + dbObject.put(DefaultTypeMapper.DEFAULT_TYPE_KEY, Person.class.getName()); assertThat(converter.read(BirthDateContainer.class, dbObject), is(BirthDateContainer.class)); } @@ -180,8 +180,8 @@ public class MappingMongoConverterUnitTests { DBObject result = new BasicDBObject(); converter.write(person, result); - assertThat(result.containsField(MappingMongoConverter.CUSTOM_TYPE_KEY), is(true)); - assertThat(result.get(MappingMongoConverter.CUSTOM_TYPE_KEY).toString(), is(Person.class.getName())); + assertThat(result.containsField(DefaultTypeMapper.DEFAULT_TYPE_KEY), is(true)); + assertThat(result.get(DefaultTypeMapper.DEFAULT_TYPE_KEY).toString(), is(Person.class.getName())); } /** @@ -295,7 +295,7 @@ public class MappingMongoConverterUnitTests { BasicDBList contacts = (BasicDBList) result; DBObject personDbObject = (DBObject) contacts.get(0); assertThat(personDbObject.get("foo").toString(), is("Oliver")); - assertThat((String) personDbObject.get(MappingMongoConverter.CUSTOM_TYPE_KEY), is(Person.class.getName())); + assertThat((String) personDbObject.get(DefaultTypeMapper.DEFAULT_TYPE_KEY), is(Person.class.getName())); } /** @@ -304,7 +304,7 @@ public class MappingMongoConverterUnitTests { @Test public void readsCollectionWithInterfaceCorrectly() { - BasicDBObject person = new BasicDBObject(MappingMongoConverter.CUSTOM_TYPE_KEY, Person.class.getName()); + BasicDBObject person = new BasicDBObject(DefaultTypeMapper.DEFAULT_TYPE_KEY, Person.class.getName()); person.put("foo", "Oliver"); BasicDBList contacts = new BasicDBList(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/StringBasedMongoQueryUnitTests.java index 92bf3536b..5eb6c18cd 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/StringBasedMongoQueryUnitTests.java @@ -28,6 +28,7 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.mongodb.MongoDbFactory; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.convert.DefaultTypeMapper; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.mapping.MongoMappingContext; @@ -93,7 +94,7 @@ public class StringBasedMongoQueryUnitTests { DBObject dbObject = new BasicDBObject(); converter.write(address, dbObject); - dbObject.removeField(MappingMongoConverter.CUSTOM_TYPE_KEY); + dbObject.removeField(DefaultTypeMapper.DEFAULT_TYPE_KEY); org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor); BasicDBObject queryObject = new BasicDBObject("address", dbObject); @@ -114,7 +115,7 @@ public class StringBasedMongoQueryUnitTests { DBObject addressDbObject = new BasicDBObject(); converter.write(address, addressDbObject); - addressDbObject.removeField(MappingMongoConverter.CUSTOM_TYPE_KEY); + addressDbObject.removeField(DefaultTypeMapper.DEFAULT_TYPE_KEY); DBObject reference = new BasicDBObject("address", addressDbObject); reference.put("lastname", "Matthews");