DATACMNS-76 - Introduced interfaces for entity conversion.

Extracted interfaces for entity conversion and type mapping from MongoDB module.
This commit is contained in:
Oliver Gierke
2011-09-22 14:53:17 +02:00
parent b97447bce8
commit 4ee29c5c6c
14 changed files with 811 additions and 14 deletions

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2011 by the original author(s).
*
* 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.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.data.mapping.PersistentEntity;
/**
* Annotation to allow {@link String} based type aliases to be used when writing type information for
* {@link PersistentEntity}s.
*
* @author Oliver Gierke
*/
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TypeAlias {
/**
* The type alias to be used when persisting
* @return
*/
String value();
}

View File

@@ -0,0 +1,105 @@
/*
* 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.convert;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
/**
* {@link TypeInformationMapper} implementation that can be either set up using a {@link MappingContext} or manually set
* up {@link Map} of {@link String} aliases to types. If a {@link MappingContext} is used the {@link Map} will be build
* inspecting the {@link PersistentEntity} instances for type alias information.
*
* @author Oliver Gierke
*/
public class ConfigurableTypeInformationMapper implements TypeInformationMapper {
private final Map<TypeInformation<?>, Object> typeMap;
/**
* Creates a {@link ConfigurableTypeInformationMapper} from the given {@link MappingContext}. Inspects all
* {@link PersistentEntity} instances for alias information and builds a {@link Map} of aliases to types from it.
*
* @param mappingContext
*/
public ConfigurableTypeInformationMapper(MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext) {
Assert.notNull(mappingContext);
this.typeMap = new HashMap<TypeInformation<?>, Object>();
for (PersistentEntity<?, ?> entity : mappingContext.getPersistentEntities()) {
Object alias = entity.getTypeAlias();
if (alias != null) {
typeMap.put(entity.getTypeInformation(), alias);
}
}
}
/**
* Creates a new {@link ConfigurableTypeMapper} for the given type map.
*
* @param sourceTypeMap must not be {@literal null}.
*/
public ConfigurableTypeInformationMapper(Map<? extends Class<?>, String> sourceTypeMap) {
Assert.notNull(sourceTypeMap);
this.typeMap = new HashMap<TypeInformation<?>, Object>(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);
}
}
/* (non-Javadoc)
* @see org.springframework.data.convert.TypeInformationMapper#createAliasFor(org.springframework.data.util.TypeInformation)
*/
public Object createAliasFor(TypeInformation<?> type) {
Object key = typeMap.get(type);
return key == null ? null : key;
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.TypeInformationMapper#resolveTypeFrom(java.lang.Object)
*/
public TypeInformation<?> resolveTypeFrom(Object alias) {
for (Entry<TypeInformation<?>, Object> entry : typeMap.entrySet()) {
if (entry.getValue().equals(alias)) {
return entry.getKey();
}
}
return null;
}
}

View File

@@ -0,0 +1,155 @@
/*
* 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.convert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
/**
* Default implementation of {@link MongoTypeMapper} 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<S> implements TypeMapper<S> {
private final TypeAliasAccessor<S> accessor;
private final List<? extends TypeInformationMapper> mappers;
public DefaultTypeMapper(TypeAliasAccessor<S> accessor) {
this(accessor, Arrays.asList(SimpleTypeInformationMapper.INSTANCE));
}
public DefaultTypeMapper(TypeAliasAccessor<S> accessor, List<? extends TypeInformationMapper> mappers) {
this(accessor, null, mappers);
}
public DefaultTypeMapper(TypeAliasAccessor<S> accessor, MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext, List<? extends TypeInformationMapper> additionalMappers) {
Assert.notNull(accessor);
List<TypeInformationMapper> mappers = new ArrayList<TypeInformationMapper>(additionalMappers.size() + 1);
if (mappingContext != null) {
mappers.add(new ConfigurableTypeInformationMapper(mappingContext));
}
mappers.addAll(additionalMappers);
this.mappers = Collections.unmodifiableList(mappers);
this.accessor = accessor;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.MongoTypeMapper#readType(com.mongodb.DBObject)
*/
public TypeInformation<?> readType(S source) {
Assert.notNull(source);
Object alias = accessor.readAliasFrom(source);
for (TypeInformationMapper mapper : mappers) {
TypeInformation<?> type = mapper.resolveTypeFrom(alias);
if (type != null) {
return type;
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.TypeMapper#readType(java.lang.Object, org.springframework.data.util.TypeInformation)
*/
@SuppressWarnings("unchecked")
public <T> TypeInformation<? extends T> readType(S source, TypeInformation<T> basicType) {
Assert.notNull(source);
Class<?> documentsTargetType = getDefaultedTypeToBeUsed(source);
if (documentsTargetType == null) {
return basicType;
}
Class<T> rawType = basicType == null ? null : basicType.getType();
boolean isMoreConcreteCustomType = rawType == null ? true : rawType.isAssignableFrom(documentsTargetType)
&& !rawType.equals(documentsTargetType);
return isMoreConcreteCustomType ? (TypeInformation<? extends T>) ClassTypeInformation.from(documentsTargetType)
: basicType;
}
/**
* Returns the type discovered through {@link #readType(Object)} but defaulted to the one returned by
* {@link #getFallbackTypeFor(Object)}.
*
* @param source
* @return
*/
private Class<?> getDefaultedTypeToBeUsed(S source) {
TypeInformation<?> documentsTargetTypeInformation = readType(source);
documentsTargetTypeInformation = documentsTargetTypeInformation == null ? getFallbackTypeFor(source) : documentsTargetTypeInformation;
return documentsTargetTypeInformation == null ? null : documentsTargetTypeInformation.getType();
}
/**
* Returns the type fallback {@link TypeInformation} in case none could be extracted from the given source.
*
* @param source will never be {@literal null}.
* @return
*/
protected TypeInformation<?> getFallbackTypeFor(S source) {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.MongoTypeMapper#writeType(java.lang.Class, com.mongodb.DBObject)
*/
public void writeType(Class<?> type, S dbObject) {
writeType(ClassTypeInformation.from(type), dbObject);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.convert.MongoTypeMapper#writeType(java.lang.Class, com.mongodb.DBObject)
*/
public void writeType(TypeInformation<?> info, S sink) {
Assert.notNull(info);
for (TypeInformationMapper mapper : mappers) {
Object alias = mapper.createAliasFor(info);
if (alias != null) {
accessor.writeTypeTo(sink, alias);
return;
}
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2011 by the original author(s).
*
* 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.convert;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
/**
*
* @param <E> the concrete {@link PersistentEntity} implementation the converter is based on.
* @param <P> the concrete {@link PersistentProperty} implementation the converter is based on.
* @param <T> the most common type the {@link EntityConverter} can actually convert.
* @param <S> the store specific source and sink an {@link EntityConverter} can deal with.
* @author Oliver Gierke
*/
public interface EntityConverter<E extends PersistentEntity<?, P>, P extends PersistentProperty<P>, T, S> extends
EntityReader<T, S>, EntityWriter<T, S> {
/**
* Returns the underlying {@link MappingContext} used by the converter.
*
* @return never {@literal null}
*/
MappingContext<? extends E, P> getMappingContext();
/**
* Returns the underlying {@link ConversionService} used by the converter.
*
* @return never {@literal null}.
*/
ConversionService getConversionService();
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2011 by the original author(s).
*
* 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.convert;
/**
* Interface to read object from store specific sources.
*
* @author Oliver Gierke
*/
public interface EntityReader<T, S> {
/**
* Reads the given source into the given type.
*
* @param type they type to convert the given source to.
* @param source the source to create an object of the given type from.
* @return
*/
<R extends T> R read(Class<R> type, S source);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2011 by the original author(s).
*
* 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.convert;
/**
* Interface to write objects into store specific sinks.
*
* @param <T> the entity type the converter can handle
* @param <S> the store specific sink the converter is able to write to
* @author Oliver Gierke
*/
public interface EntityWriter<T, S> {
void write(T source, S sink);
}

View File

@@ -0,0 +1,73 @@
/*
* 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.convert;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* Basic {@link TypeInformationMapper} implementation that interprets the alias handles as fully qualified class name
* and tries to load a class with the given name to build {@link TypeInformation}. Returns the fully qualified class
* name for alias creation.
*
* @author Oliver Gierke
*/
public class SimpleTypeInformationMapper implements TypeInformationMapper {
public static final SimpleTypeInformationMapper INSTANCE = new SimpleTypeInformationMapper();
/**
* Returns the {@link TypeInformation} that shall be used when the given {@link String} value is found as type hint.
* The 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.
*
* @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.
*/
public TypeInformation<?> resolveTypeFrom(Object source) {
if (!(source instanceof String)) {
return null;
}
String value = (String) source;
if (!StringUtils.hasText(value)) {
return null;
}
try {
return ClassTypeInformation.from(ClassUtils.forName(value, null));
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Turn the given type information into the String representation that shall 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.
*/
public String createAliasFor(TypeInformation<?> type) {
return type == null ? null : type.getType().getName();
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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.convert;
/**
* Interface to abstract implementations of how to access a type alias from a given source or sink.
*
* @author Oliver Gierke
*/
public interface TypeAliasAccessor<S> {
/**
* Reads the type alias to be used from the given source.
*
* @param source
* @return
*/
Object readAliasFrom(S source);
/**
* Writes the given type alias to the given sink.
*
* @param sink
* @param alias
*/
void writeTypeTo(S sink, Object alias);
}

View File

@@ -0,0 +1,42 @@
/*
* 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.convert;
import org.springframework.data.util.TypeInformation;
/**
* Interface to abstract the mapping from a type alias to the actual type.
*
* @author Oliver Gierke
*/
public interface TypeInformationMapper {
/**
* Returns the actual {@link TypeInformation} to be used for the given alias.
*
* @param alias
* @return
*/
TypeInformation<?> resolveTypeFrom(Object alias);
/**
* Returns the alias to be used for the given {@link TypeInformation}.
*
* @param type
* @return
*/
Object createAliasFor(TypeInformation<?> type);
}

View File

@@ -0,0 +1,59 @@
/*
* 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.convert;
import org.springframework.data.util.TypeInformation;
/**
* Interface to define strategies how to store type information in a store specific sink or source.
*
* @author Oliver Gierke
*/
public interface TypeMapper<S> {
/**
* Reads the {@link TypeInformation} from the given source.
*
* @param source must not be {@literal null}.
* @return
*/
TypeInformation<?> readType(S source);
/**
* Returns the {@link TypeInformation} from the given source if it is a more concrete type than the given default one.
*
* @param source must not be {@literal null}.
* @param defaultType
* @return
*/
<T> TypeInformation<? extends T> readType(S source, TypeInformation<T> defaultType);
/**
* Writes type information for the given type into the given sink.
*
* @param type must not be {@literal null}.
* @param dbObject must not be {@literal null}.
*/
void writeType(Class<?> type, S dbObject);
/**
* Writes type information for the given {@link TypeInformation} into the given sink.
*
* @param type must not be {@literal null}.
* @param dbObject must not be {@literal null}.
*/
void writeType(TypeInformation<?> type, S dbObject);
}

View File

@@ -47,6 +47,14 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> {
* @return The underlying Java class for this entity
*/
Class<T> getType();
/**
* Returns the alias to be used when storing type information. Might be {@literal null} to indicate that there was no
* alias defined through the mapping metadata.
*
* @return
*/
Object getTypeAlias();
/**
* Returns the {@link TypeInformation} backing this {@link PersistentEntity}.

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011 by the original author(s).
* Copyright 2011 by the original author(s).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.PersistentEntity;
@@ -29,6 +30,7 @@ import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Simple value object to capture information of {@link PersistentEntity}s.
@@ -141,6 +143,16 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
public Class<T> getType() {
return information.getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentEntity#getTypeAlias()
*/
public Object getTypeAlias() {
TypeAlias alias = getType().getAnnotation(TypeAlias.class);
return alias == null ? null : StringUtils.hasText(alias.value()) ? alias.value() : null;
}
/*
* (non-Javadoc)

View File

@@ -0,0 +1,123 @@
/*
* 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.convert;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
/**
* Unit tests for {@link ConfigurableTypeMapper}.
*
* @author Oliver Gierke
*/
public class ConfigurableTypeInformationMapperUnitTests<T extends PersistentProperty<T>> {
ConfigurableTypeInformationMapper mapper;
@Before
public void setUp() {
mapper = new ConfigurableTypeInformationMapper(Collections.singletonMap(String.class, "1"));
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullTypeMap() {
new ConfigurableTypeInformationMapper((Map<? extends Class<?>, String>) null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullMappingContext() {
new ConfigurableTypeInformationMapper((MappingContext<?, ?>) 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 ConfigurableTypeInformationMapper(map);
}
@Test
@SuppressWarnings("unchecked")
public void extractsAliasInfoFromMappingContext() {
AbstractMappingContext<BasicPersistentEntity<Object,T>,T> mappingContext = new AbstractMappingContext<BasicPersistentEntity<Object, T>, T>() {
@Override
protected <S> BasicPersistentEntity<Object, T> createPersistentEntity(TypeInformation<S> typeInformation) {
return (BasicPersistentEntity<Object, T>) new BasicPersistentEntity<S, T>(typeInformation);
}
@Override
protected T createPersistentProperty(Field field, PropertyDescriptor descriptor,
BasicPersistentEntity<Object, T> owner, SimpleTypeHolder simpleTypeHolder) {
return (T) new AnnotationBasedPersistentProperty<T>(field, descriptor, owner, simpleTypeHolder) {
@Override
protected Association<T> createAssociation() {
return null;
}
};
}
};
mappingContext.setInitialEntitySet(Collections.singleton(Entity.class));
mappingContext.afterPropertiesSet();
mapper = new ConfigurableTypeInformationMapper(mappingContext);
assertThat(mapper.createAliasFor(ClassTypeInformation.from(Entity.class)), is((Object) "foo"));
}
@Test
public void writesMapKeyForType() {
assertThat(mapper.createAliasFor(ClassTypeInformation.from(String.class)), is((Object) "1"));
assertThat(mapper.createAliasFor(ClassTypeInformation.from(Object.class)), is(nullValue()));
}
@Test
@SuppressWarnings("rawtypes")
public void readsTypeForMapKey() {
assertThat(mapper.resolveTypeFrom("1"), is((TypeInformation) ClassTypeInformation.from(String.class)));
assertThat(mapper.resolveTypeFrom("unmapped"), is(nullValue()));
}
@TypeAlias("foo")
class Entity {
}
}

View File

@@ -10,6 +10,8 @@ import java.util.SortedSet;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentEntitySpec;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.Person;
@@ -18,7 +20,7 @@ import org.springframework.test.util.ReflectionTestUtils;
/**
* Unit test for {@link BasicPersistentEntity}.
*
*
* @author Oliver Gierke
*/
public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
@@ -27,53 +29,77 @@ public class BasicPersistentEntityUnitTests<T extends PersistentProperty<T>> {
public void assertInvariants() {
PersistentEntitySpec.assertInvariants(createEntity(null));
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullTypeInformation() {
new BasicPersistentEntity<Object, T>(null);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullProperty() {
createEntity(null).addPersistentProperty(null);
}
@Test
public void returnsNullForTypeAliasIfNoneConfigured() {
PersistentEntity<Entity, T> entity = new BasicPersistentEntity<Entity, T>(ClassTypeInformation.from(Entity.class));
assertThat(entity.getTypeAlias(), is(nullValue()));
}
@Test
public void returnsTypeAliasIfAnnotated() {
PersistentEntity<AliasedEntity, T> entity = new BasicPersistentEntity<AliasedEntity, T>(
ClassTypeInformation.from(AliasedEntity.class));
assertThat(entity.getTypeAlias(), is((Object) "foo"));
}
/**
* @see DATACMNS-50
*/
@Test
@SuppressWarnings("unchecked")
public void considersComparatorForPropertyOrder() {
BasicPersistentEntity<Person,T> entity = createEntity(new Comparator<T>() {
BasicPersistentEntity<Person, T> entity = createEntity(new Comparator<T>() {
public int compare(T o1, T o2) {
return o1.getName().compareTo(o2.getName());
}
});
T lastName = (T) Mockito.mock(PersistentProperty.class);
when(lastName.getName()).thenReturn("lastName");
T firstName = (T) Mockito.mock(PersistentProperty.class);
when(firstName.getName()).thenReturn("firstName");
T ssn = (T) Mockito.mock(PersistentProperty.class);
when(ssn.getName()).thenReturn("ssn");
entity.addPersistentProperty(lastName);
entity.addPersistentProperty(firstName);
entity.addPersistentProperty(ssn);
SortedSet<T> properties = (SortedSet<T>) ReflectionTestUtils.getField(entity, "properties");
assertThat(properties.size(), is(3));
Iterator<T> iterator = properties.iterator();
assertThat(iterator.next(), is(entity.getPersistentProperty("firstName")));
assertThat(iterator.next(), is(entity.getPersistentProperty("lastName")));
assertThat(iterator.next(), is(entity.getPersistentProperty("ssn")));
}
private BasicPersistentEntity<Person, T> createEntity(Comparator<T> comparator) {
return new BasicPersistentEntity<Person, T>(ClassTypeInformation.from(Person.class), comparator);
}
@TypeAlias("foo")
static class AliasedEntity {
}
static class Entity {
}
}