DATADOC-63 - Added TypeMapper abstraction to customize how type information is written to a DBObject and retrieved from it.

Added TypeMapper abstraction to allow plugging in a custom implementation. Current type handling logic is now in DefaultTypeMapper which additionally allows customizing the key the type information its stored under or disabling writing type info by setting the key to null.

Added ConfigurableTypeMapper implementation that gets a Map configured to define Strings to be used to store type information.
This commit is contained in:
Oliver Gierke
2011-08-26 20:20:38 +02:00
parent 95245015bc
commit ce5046c35f
10 changed files with 644 additions and 49 deletions

View File

@@ -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<TypeInformation<?>, 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<? extends Class<?>, String> sourceTypeMap) {
Assert.notNull(sourceTypeMap);
this.typeMap = new HashMap<TypeInformation<?>, String>(sourceTypeMap.size());
for (Entry<? extends Class<?>, 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<TypeInformation<?>, 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;
}
}

View File

@@ -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> 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;
}
}
}

View File

@@ -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 <jbrisbin@vmware.com>
* @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> 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<String, Object> sourceMap = dbObject.toMap();
for (Entry<String, Object> 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;

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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<Object> writer;
private final MongoWriter<?> writer;
private final MongoParameterAccessor delegate;
/**
@@ -42,7 +43,7 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
*
* @param writer
*/
public ConvertingParameterAccessor(MongoWriter<Object> 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;
}

View File

@@ -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<Class<?>, String> map = new HashMap<Class<?>, 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));
}
}
}

View File

@@ -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()));
}
}
}

View File

@@ -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();

View File

@@ -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");