DATACMNS-38 - Prevent StackOverflow on self referencing objects.

Various improvements and additional test cases for the TypeInformation abstraction. Introduced ParameterizedTypeInformation in-between class to take the parent into account when calculating equals(…) and hashCode(). We're also shortcutting TypeInformation creation if the parent's underlying type equals the one we shall create a new TypeInformation for.

Slightly modified the parsing algorithm when adding PersistentEntity objects. We now eagerly add them to the cache to prevent endless recursive adding.
This commit is contained in:
Oliver Gierke
2011-05-17 20:11:50 +01:00
parent 66c77820c4
commit 7a110d593e
12 changed files with 426 additions and 103 deletions

View File

@@ -162,6 +162,10 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
try {
final E entity = createPersistentEntity(typeInformation);
// Eagerly cache the entity as we might have to find it during recursive lookups.
persistentEntities.put(entity.getTypeInformation(), entity);
BeanInfo info = Introspector.getBeanInfo(type);
final Map<String, PropertyDescriptor> descriptors = new HashMap<String, PropertyDescriptor>();
@@ -196,8 +200,6 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
if (nestedType != null) {
addPersistentEntity(nestedType);
}
}
}, new ReflectionUtils.FieldFilter() {
public boolean matches(Field field) {
@@ -212,9 +214,6 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
applicationEventPublisher.publishEvent(new MappingContextEvent<E, P>(entity, typeInformation));
}
// Cache
persistentEntities.put(entity.getTypeInformation(), (E) entity);
return entity;
} catch (IntrospectionException e) {
throw new MappingException(e.getMessage(), e);
@@ -270,7 +269,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
protected abstract P createPersistentProperty(Field field, PropertyDescriptor descriptor, E owner);
public void afterPropertiesSet() throws Exception {
public void afterPropertiesSet() {
for (Class<?> initialEntity : initialEntitySet) {
addPersistentEntity(initialEntity);
}

View File

@@ -1,3 +1,18 @@
/*
* 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.repository;
import java.io.Serializable;

View File

@@ -1,3 +1,18 @@
/*
* 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.repository.support;
import org.springframework.data.repository.RepositoryProxy;

View File

@@ -1,45 +0,0 @@
package org.springframework.data.util;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
/**
* Special {@link TypeDiscoverer} handling {@link GenericArrayType}s.
*
* @author Oliver Gierke
*/
public class ArrayTypeDiscoverer<S> extends TypeDiscoverer<S> {
private GenericArrayType type;
/**
* @param type
* @param parent
* @param parent
*/
protected ArrayTypeDiscoverer(GenericArrayType type, TypeDiscoverer<?> parent) {
super(type, null, parent);
this.type = type;
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getType()
*/
@Override
@SuppressWarnings("unchecked")
public Class<S> getType() {
return (Class<S>) Array.newInstance(resolveType(type.getGenericComponentType()), 0).getClass();
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getComponentType()
*/
@Override
public TypeInformation<?> getComponentType() {
Type componentType = type.getGenericComponentType();
return createInfo(componentType);
}
}

View File

@@ -1,3 +1,18 @@
/*
* 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.util;
import java.lang.reflect.Type;
@@ -15,6 +30,13 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
private final Class<S> type;
/**
* Simple factory method to easily create new instances of {@link ClassTypeInformation}.
*
* @param <S>
* @param type
* @return
*/
public static <S> TypeInformation<S> from(Class<S> type) {
return new ClassTypeInformation<S>(type);
}
@@ -25,16 +47,13 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
* @param type
*/
public ClassTypeInformation(Class<S> type) {
this(type, GenericTypeResolver.getTypeVariableMap(type), null);
this(type, GenericTypeResolver.getTypeVariableMap(type));
}
ClassTypeInformation(Class<S> type, TypeDiscoverer<?> parent) {
this(type, null, parent);
}
@SuppressWarnings("rawtypes") ClassTypeInformation(Class<S> type, Map<TypeVariable, Type> typeVariableMap,
TypeDiscoverer<?> parent) {
super(type, typeVariableMap, parent);
@SuppressWarnings("rawtypes")
ClassTypeInformation(Class<S> type, Map<TypeVariable, Type> typeVariableMap) {
super(type, typeVariableMap);
this.type = type;
}
@@ -68,27 +87,4 @@ public class ClassTypeInformation<S> extends TypeDiscoverer<S> {
Class<?> componentType = type.getComponentType();
return componentType.isArray() ? resolveArrayType(componentType) : componentType;
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
ClassTypeInformation<?> that = (ClassTypeInformation<?>) obj;
return this.type.equals(that.type);
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#hashCode()
*/
@Override
public int hashCode() {
int result = super.hashCode();
return result += 31 * type.hashCode();
}
}

View File

@@ -0,0 +1,62 @@
/*
* 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.util;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
/**
* Special {@link TypeDiscoverer} handling {@link GenericArrayType}s.
*
* @author Oliver Gierke
*/
class GenericArrayTypeInformation<S> extends ParameterizedTypeInformation<S> {
private GenericArrayType type;
/**
* Creates a new {@link GenericArrayTypeInformation} for the given {@link GenericArrayTypeInformation} and
* {@link TypeDiscoverer}.
*
* @param type
* @param parent
*/
protected GenericArrayTypeInformation(GenericArrayType type, TypeDiscoverer<?> parent) {
super(type, parent);
this.type = type;
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getType()
*/
@Override
@SuppressWarnings("unchecked")
public Class<S> getType() {
return (Class<S>) Array.newInstance(resolveType(type.getGenericComponentType()), 0).getClass();
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#getComponentType()
*/
@Override
public TypeInformation<?> getComponentType() {
Type componentType = type.getGenericComponentType();
return createInfo(componentType);
}
}

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.util;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Base class for all types that include parameterization of some kind. Crucial as we have to take note of the parent
* class we will have to resolve generic parameters against.
*
* @author Oliver Gierke
*/
class ParameterizedTypeInformation<T> extends TypeDiscoverer<T> {
private final TypeDiscoverer<?> parent;
/**
* Creates a new {@link ParameterizedTypeInformation} for the given {@link Type} and parent {@link TypeDiscoverer}.
*
* @param type must not be {@literal null}
* @param parent must not be {@literal null}
*/
public ParameterizedTypeInformation(Type type, TypeDiscoverer<?> parent) {
super(type, null);
Assert.notNull(parent);
this.parent = parent;
}
/**
* Considers the parent's type variable map before invoking the super class method.
*
* @return
*/
@SuppressWarnings("rawtypes")
protected Map<TypeVariable, Type> getTypeVariableMap() {
return parent != null ? parent.getTypeVariableMap() : super.getTypeVariableMap();
}
/* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#createInfo(java.lang.reflect.Type)
*/
@Override
protected TypeInformation<?> createInfo(Type fieldType) {
if (parent.getType().equals(fieldType)) {
return parent;
}
return super.createInfo(fieldType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
if (!this.getClass().equals(obj.getClass())) {
return false;
}
ParameterizedTypeInformation<?> that = (ParameterizedTypeInformation<?>) obj;
return this.parent == null ? that.parent == null : this.parent.equals(that.parent);
}
/*
* (non-Javadoc)
* @see org.springframework.data.util.TypeDiscoverer#hashCode()
*/
@Override
public int hashCode() {
return super.hashCode() + 31 * ObjectUtils.nullSafeHashCode(parent);
}
}

View File

@@ -1,3 +1,18 @@
/*
* 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.util;
import static org.springframework.util.ObjectUtils.*;
@@ -28,23 +43,19 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
@SuppressWarnings("rawtypes")
private final Map<TypeVariable, Type> typeVariableMap;
private final Map<String, TypeInformation<?>> fieldTypes = new ConcurrentHashMap<String, TypeInformation<?>>();
private final TypeDiscoverer<?> parent;
/**
* Creates a ne {@link TypeDiscoverer} for the given type, type variable map and parent.
*
* @param type must not be null.
* @param type must not be null.
* @param typeVariableMap
* @param parent
*/
@SuppressWarnings("rawtypes")
protected TypeDiscoverer(Type type, Map<TypeVariable, Type> typeVariableMap,
TypeDiscoverer<?> parent) {
protected TypeDiscoverer(Type type, Map<TypeVariable, Type> typeVariableMap) {
Assert.notNull(type);
this.type = type;
this.typeVariableMap = typeVariableMap;
this.parent = parent;
}
/**
@@ -54,9 +65,9 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
* @return
*/
@SuppressWarnings("rawtypes")
private Map<TypeVariable, Type> getTypeVariableMap() {
protected Map<TypeVariable, Type> getTypeVariableMap() {
return parent != null ? parent.getTypeVariableMap() : typeVariableMap;
return typeVariableMap;
}
/**
@@ -71,10 +82,14 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
if (fieldType.equals(this.type)) {
return this;
}
if (fieldType instanceof Class) {
return new ClassTypeInformation((Class<?>) fieldType);
}
if (fieldType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) fieldType;
return new TypeDiscoverer(parameterizedType, null, this);
return new ParameterizedTypeInformation(parameterizedType, this);
}
if (fieldType instanceof TypeVariable) {
@@ -82,12 +97,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
return new TypeVariableTypeInformation(variable, type, this);
}
if (fieldType instanceof Class) {
return new ClassTypeInformation((Class<?>) fieldType, this);
}
if (fieldType instanceof GenericArrayType) {
return new ArrayTypeDiscoverer((GenericArrayType) fieldType, this);
return new GenericArrayTypeInformation((GenericArrayType) fieldType, this);
}
throw new IllegalArgumentException();
@@ -145,7 +156,7 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
String head = fieldname.substring(0, separatorIndex);
TypeInformation<?> info = fieldTypes.get(head);
return info.getProperty(fieldname.substring(separatorIndex + 1));
return info == null ? null : info.getProperty(fieldname.substring(separatorIndex + 1));
}
private TypeInformation<?> getPropertyInformation(String fieldname) {
@@ -237,9 +248,8 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
boolean typeEqual = nullSafeEquals(this.type, that.type);
boolean typeVariableMapEqual = nullSafeEquals(this.typeVariableMap,
that.typeVariableMap);
boolean parentEqual = nullSafeEquals(this.parent, that.parent);
return typeEqual && typeVariableMapEqual && parentEqual;
return typeEqual && typeVariableMapEqual;
}
/*
@@ -253,7 +263,6 @@ class TypeDiscoverer<S> implements TypeInformation<S> {
int result = 17;
result += nullSafeHashCode(type);
result += nullSafeHashCode(typeVariableMap);
result += nullSafeHashCode(parent);
return result;
}
}

View File

@@ -1,3 +1,18 @@
/*
* 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.util;
import static org.springframework.util.ObjectUtils.*;
@@ -14,7 +29,7 @@ import org.springframework.util.Assert;
*
* @author Oliver Gierke
*/
class TypeVariableTypeInformation<T> extends TypeDiscoverer<T> {
class TypeVariableTypeInformation<T> extends ParameterizedTypeInformation<T> {
private final TypeVariable<?> variable;
private final Type owningType;
@@ -29,7 +44,7 @@ class TypeVariableTypeInformation<T> extends TypeDiscoverer<T> {
*/
public TypeVariableTypeInformation(TypeVariable<?> variable, Type owningType, TypeDiscoverer<?> parent) {
super(variable, null, parent);
super(variable, parent);
Assert.notNull(variable);
this.variable = variable;
this.owningType = owningType;

View File

@@ -1,3 +1,18 @@
/*
* 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.util;
import static org.hamcrest.CoreMatchers.*;
@@ -109,6 +124,15 @@ public class ClassTypeInformationUnitTests {
assertEquals(Map.class, map.getType());
assertEquals(Calendar.class, map.getMapValueType().getType());
}
@Test
public void typeInfoDoesNotEqualForGenericTypesWithDifferentParent() {
TypeInformation<ConcreteWrapper> first = ClassTypeInformation.from(ConcreteWrapper.class);
TypeInformation<AnotherConcreteWrapper> second = ClassTypeInformation.from(AnotherConcreteWrapper.class);
assertFalse(first.getProperty("wrapped").equals(second.getProperty("wrapped")));
}
static class StringMapContainer extends MapContainer<String> {
@@ -171,4 +195,8 @@ public class ClassTypeInformationUnitTests {
static class ConcreteWrapper extends GenericWrapper<String> {
}
static class AnotherConcreteWrapper extends GenericWrapper<Long> {
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.util;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Unit tests for {@link ParameterizedTypeInformation}.
*
* @author Oliver Gierke
*/
public class ParameterizedTypeUnitTests {
@Test
public void considersTypeInformationsWithDifferingParentsNotEqual() {
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
TypeDiscoverer<Object> objectParent = new TypeDiscoverer<Object>(Object.class, null);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(Object.class, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(Object.class, objectParent);
assertFalse(first.equals(second));
}
@Test
public void considersTypeInformationsWithSameParentsNotEqual() {
TypeDiscoverer<String> stringParent = new TypeDiscoverer<String>(String.class, null);
ParameterizedTypeInformation<Object> first = new ParameterizedTypeInformation<Object>(Object.class, stringParent);
ParameterizedTypeInformation<Object> second = new ParameterizedTypeInformation<Object>(Object.class, stringParent);
assertTrue(first.equals(second));
}
}

View File

@@ -1,16 +1,96 @@
/*
* 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.util;
import static org.junit.Assert.*;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link TypeDiscoverer}.
*
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class TypeDiscovererUnitTests {
@Mock
@SuppressWarnings("rawtypes")
Map<TypeVariable, Type> firstMap;
@Mock
@SuppressWarnings("rawtypes")
Map<TypeVariable, Type> secondMap;
@Test(expected = IllegalArgumentException.class)
public void rejectsNullType() {
new TypeDiscoverer<Object>(null, null, null);
new TypeDiscoverer<Object>(null, null);
}
@Test
public void isNotEqualIfTypesDiffer() {
TypeDiscoverer<Object> objectTypeInfo = new TypeDiscoverer<Object>(Object.class, null);
TypeDiscoverer<String> stringTypeInfo = new TypeDiscoverer<String>(String.class, null);
assertFalse(objectTypeInfo.equals(stringTypeInfo));
}
@Test
public void isNotEqualIfTypeVariableMapsDiffer() {
assertFalse(firstMap.equals(secondMap));
TypeDiscoverer<Object> first = new TypeDiscoverer<Object>(Object.class, firstMap);
TypeDiscoverer<Object> second = new TypeDiscoverer<Object>(Object.class, secondMap);
assertFalse(first.equals(second));
}
@Test
public void dealsWithTypesReferencingThemselves() {
TypeInformation<SelfReferencing> information = new ClassTypeInformation<SelfReferencing>(SelfReferencing.class);
TypeInformation<?> first = information.getProperty("parent").getMapValueType();
TypeInformation<?> second = first.getProperty("map").getMapValueType();
assertEquals(first, second);
}
@Test
public void dealsWithTypesReferencingThemselvesInAMap() {
TypeInformation<SelfReferencingMap> information = new ClassTypeInformation<SelfReferencingMap>(
SelfReferencingMap.class);
TypeInformation<?> mapValueType = information.getProperty("map").getMapValueType();
assertEquals(mapValueType, information);
}
class SelfReferencing {
Map<String, SelfReferencingMap> parent;
}
class SelfReferencingMap {
Map<String, SelfReferencingMap> map;
}
}