Merge pull request #110 from spaetzold/master

Type safety with find methods
This commit is contained in:
Michael Hunger
2013-07-19 09:40:35 -07:00
11 changed files with 295 additions and 6 deletions

View File

@@ -56,6 +56,7 @@ import org.springframework.data.neo4j.support.relationship.RelationshipEntityIns
import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory; import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory;
import org.springframework.data.neo4j.support.typerepresentation.ClassValueTypeInformationMapper; import org.springframework.data.neo4j.support.typerepresentation.ClassValueTypeInformationMapper;
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
import org.springframework.data.support.IsNewStrategyFactory; import org.springframework.data.support.IsNewStrategyFactory;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import javax.validation.Validator; import javax.validation.Validator;
@@ -117,6 +118,7 @@ public abstract class Neo4jConfiguration {
factoryBean.setTransactionManager(neo4jTransactionManager()); factoryBean.setTransactionManager(neo4jTransactionManager());
factoryBean.setGraphDatabase(graphDatabase()); factoryBean.setGraphDatabase(graphDatabase());
factoryBean.setIsNewStrategyFactory(isNewStrategyFactory()); factoryBean.setIsNewStrategyFactory(isNewStrategyFactory());
factoryBean.setTypeSafetyPolicy(typeSafetyPolicy());
factoryBean.setIndexProvider(indexProvider()); factoryBean.setIndexProvider(indexProvider());
@@ -269,6 +271,11 @@ public abstract class Neo4jConfiguration {
return new IndexProviderImpl(graphDatabase()); return new IndexProviderImpl(graphDatabase());
} }
@Bean
public TypeSafetyPolicy typeSafetyPolicy() throws Exception {
return new TypeSafetyPolicy();
}
public Set<? extends Class<?>> getInitialEntitySet() { public Set<? extends Class<?>> getInitialEntitySet() {
return initialEntitySet; return initialEntitySet;
} }

View File

@@ -32,6 +32,7 @@ import org.springframework.data.neo4j.support.node.EntityStateFactory;
import org.springframework.data.neo4j.support.query.CypherQueryExecutor; import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies;
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import javax.validation.Validator; import javax.validation.Validator;
@@ -68,4 +69,6 @@ public interface Infrastructure {
TypeRepresentationStrategy<Node> getNodeTypeRepresentationStrategy(); TypeRepresentationStrategy<Node> getNodeTypeRepresentationStrategy();
TypeRepresentationStrategy<Relationship> getRelationshipTypeRepresentationStrategy(); TypeRepresentationStrategy<Relationship> getRelationshipTypeRepresentationStrategy();
TypeSafetyPolicy getTypeSafetyPolicy();
} }

View File

@@ -32,6 +32,7 @@ import org.springframework.data.neo4j.support.node.EntityStateFactory;
import org.springframework.data.neo4j.support.query.CypherQueryExecutor; import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies;
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import javax.validation.Validator; import javax.validation.Validator;
@@ -59,8 +60,9 @@ public class MappingInfrastructure implements Infrastructure {
private final IndexProvider indexProvider; private final IndexProvider indexProvider;
private final GraphDatabaseService graphDatabaseService; private final GraphDatabaseService graphDatabaseService;
private final GraphDatabase graphDatabase; private final GraphDatabase graphDatabase;
private final TypeSafetyPolicy typeSafetyPolicy;
public MappingInfrastructure(GraphDatabase graphDatabase, GraphDatabaseService graphDatabaseService, IndexProvider indexProvider, ResultConverter resultConverter, PlatformTransactionManager transactionManager, TypeRepresentationStrategies typeRepresentationStrategies, EntityRemover entityRemover, Neo4jEntityPersister entityPersister, EntityStateHandler entityStateHandler, CypherQueryExecutor cypherQueryExecutor, Neo4jMappingContext mappingContext, TypeRepresentationStrategy<Relationship> relationshipTypeRepresentationStrategy, TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy, Validator validator, ConversionService conversionService) { public MappingInfrastructure(GraphDatabase graphDatabase, GraphDatabaseService graphDatabaseService, IndexProvider indexProvider, ResultConverter resultConverter, PlatformTransactionManager transactionManager, TypeRepresentationStrategies typeRepresentationStrategies, EntityRemover entityRemover, Neo4jEntityPersister entityPersister, EntityStateHandler entityStateHandler, CypherQueryExecutor cypherQueryExecutor, Neo4jMappingContext mappingContext, TypeRepresentationStrategy<Relationship> relationshipTypeRepresentationStrategy, TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy, Validator validator, ConversionService conversionService, TypeSafetyPolicy typeSafetyPolicy) {
this.graphDatabase = graphDatabase; this.graphDatabase = graphDatabase;
this.graphDatabaseService = graphDatabaseService; this.graphDatabaseService = graphDatabaseService;
this.indexProvider = indexProvider; this.indexProvider = indexProvider;
@@ -76,6 +78,7 @@ public class MappingInfrastructure implements Infrastructure {
this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy; this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy;
this.validator = validator; this.validator = validator;
this.conversionService = conversionService; this.conversionService = conversionService;
this.typeSafetyPolicy = typeSafetyPolicy;
} }
@Override @Override
@@ -147,4 +150,9 @@ public class MappingInfrastructure implements Infrastructure {
public TypeRepresentationStrategy<Relationship> getRelationshipTypeRepresentationStrategy() { public TypeRepresentationStrategy<Relationship> getRelationshipTypeRepresentationStrategy() {
return relationshipTypeRepresentationStrategy; return relationshipTypeRepresentationStrategy;
} }
@Override
public TypeSafetyPolicy getTypeSafetyPolicy() {
return typeSafetyPolicy;
}
} }

View File

@@ -46,6 +46,7 @@ import org.springframework.data.neo4j.support.relationship.RelationshipEntityIns
import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory; import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory;
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies;
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
import org.springframework.data.support.IsNewStrategyFactory; import org.springframework.data.support.IsNewStrategyFactory;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager; import org.springframework.transaction.jta.JtaTransactionManager;
@@ -79,6 +80,7 @@ public class MappingInfrastructureFactoryBean implements FactoryBean<Infrastruct
private GraphDatabaseService graphDatabaseService; private GraphDatabaseService graphDatabaseService;
private GraphDatabase graphDatabase; private GraphDatabase graphDatabase;
private IsNewStrategyFactory isNewStrategyFactory; private IsNewStrategyFactory isNewStrategyFactory;
private TypeSafetyPolicy typeSafetyPolicy;
private MappingInfrastructure mappingInfrastructure; private MappingInfrastructure mappingInfrastructure;
private TypeRepresentationStrategyFactory.Strategy typeRepresentationStrategy; private TypeRepresentationStrategyFactory.Strategy typeRepresentationStrategy;
@@ -153,7 +155,10 @@ public class MappingInfrastructureFactoryBean implements FactoryBean<Infrastruct
if (this.indexProvider == null) { if (this.indexProvider == null) {
this.indexProvider = new IndexProviderImpl(graphDatabase); this.indexProvider = new IndexProviderImpl(graphDatabase);
} }
this.mappingInfrastructure = new MappingInfrastructure(graphDatabase, graphDatabaseService, indexProvider, resultConverter, transactionManager, typeRepresentationStrategies, entityRemover, entityPersister, entityStateHandler, cypherQueryExecutor, mappingContext, relationshipTypeRepresentationStrategy, nodeTypeRepresentationStrategy, validator, conversionService); if (this.typeSafetyPolicy == null) {
this.typeSafetyPolicy = new TypeSafetyPolicy();
}
this.mappingInfrastructure = new MappingInfrastructure(graphDatabase, graphDatabaseService, indexProvider, resultConverter, transactionManager, typeRepresentationStrategies, entityRemover, entityPersister, entityStateHandler, cypherQueryExecutor, mappingContext, relationshipTypeRepresentationStrategy, nodeTypeRepresentationStrategy, validator, conversionService, typeSafetyPolicy);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("error initializing "+getClass().getName(),e); throw new RuntimeException("error initializing "+getClass().getName(),e);
} }
@@ -293,6 +298,14 @@ public class MappingInfrastructureFactoryBean implements FactoryBean<Infrastruct
isNewStrategyFactory = newStrategyFactory; isNewStrategyFactory = newStrategyFactory;
} }
public void setTypeSafetyPolicy(TypeSafetyPolicy typeSafetyPolicy) {
this.typeSafetyPolicy = typeSafetyPolicy;
}
public TypeSafetyPolicy getTypeSafetyPolicy() {
return typeSafetyPolicy;
}
@Override @Override
public Infrastructure getObject() { public Infrastructure getObject() {
return mappingInfrastructure; return mappingInfrastructure;

View File

@@ -27,6 +27,8 @@ import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.mapping.*; import org.springframework.data.neo4j.mapping.*;
import org.springframework.data.neo4j.mapping.ManagedEntity; import org.springframework.data.neo4j.mapping.ManagedEntity;
import org.springframework.data.neo4j.support.Neo4jTemplate; import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.typesafety.TypeSafetyOption;
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
@@ -78,14 +80,24 @@ public class Neo4jEntityConverterImpl<T,S extends PropertyContainer> implements
// retrieve meta-information about the type // retrieve meta-information about the type
@SuppressWarnings("unchecked") final Neo4jPersistentEntityImpl<R> persistentEntity = (Neo4jPersistentEntityImpl<R>) mappingContext.getPersistentEntity(targetType); @SuppressWarnings("unchecked") final Neo4jPersistentEntityImpl<R> persistentEntity = (Neo4jPersistentEntityImpl<R>) mappingContext.getPersistentEntity(targetType);
if (mappingPolicy==null) { // 4) check type safety
mappingPolicy = persistentEntity.getMappingPolicy(); TypeSafetyPolicy typeSafetyPolicy = template.getInfrastructure().getTypeSafetyPolicy();
if (typeSafetyPolicy.isTypeSafetyEnabled() && !storedAndRequestedTypesMatch(requestedType, source)) {
if (typeSafetyPolicy.getTypeSafetyOption() == TypeSafetyOption.RETURNS_NULL) {
return null;
}
if (typeSafetyPolicy.getTypeSafetyOption() == TypeSafetyOption.THROWS_EXCEPTION) {
throw new InvalidEntityTypeException("Requested a entity of type '" + requestedType + "', but the stored entity is of type '" + typeMapper.readType(source).getType() + "'.");
}
} }
// 4) create object instance // 5) create object instance
if (mappingPolicy == null) {
mappingPolicy = persistentEntity.getMappingPolicy();
}
final R createdEntity = entityInstantiator.createEntityFromState(source, targetType.getType(), mappingPolicy); final R createdEntity = entityInstantiator.createEntityFromState(source, targetType.getType(), mappingPolicy);
// 5) connect state // 6) connect state
entityStateHandler.setPersistentState(createdEntity,source); entityStateHandler.setPersistentState(createdEntity,source);
if (persistentEntity.isManaged()) return createdEntity; if (persistentEntity.isManaged()) return createdEntity;
@@ -104,6 +116,11 @@ public class Neo4jEntityConverterImpl<T,S extends PropertyContainer> implements
return entity; return entity;
} }
private <R extends T> boolean storedAndRequestedTypesMatch(Class<R> requestedType, S source) {
TypeInformation<?> storedType = typeMapper.readType(source);
return storedType.getType().isAssignableFrom(requestedType);
}
private <R extends T> void cascadeFetch(Neo4jPersistentEntityImpl<R> persistentEntity, final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper, final MappingPolicy policy, final Neo4jTemplate template) { private <R extends T> void cascadeFetch(Neo4jPersistentEntityImpl<R> persistentEntity, final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper, final MappingPolicy policy, final Neo4jTemplate template) {
persistentEntity.doWithAssociations(new AssociationHandler<Neo4jPersistentProperty>() { persistentEntity.doWithAssociations(new AssociationHandler<Neo4jPersistentProperty>() {
@Override @Override

View File

@@ -0,0 +1,19 @@
package org.springframework.data.neo4j.support.typesafety;
/**
* Enum of all type safety options which are available in the system.
*
* @author spaetzold
*/
public enum TypeSafetyOption {
/** Sets the system to not be type safe (default setting). */
NONE,
/** Sets the system to return null if a entity should be loaded which is not of the requested type. */
RETURNS_NULL,
/** Sets the system to throw an exception if a entity should be loaded which is not of the requested type. */
THROWS_EXCEPTION
}

View File

@@ -0,0 +1,49 @@
package org.springframework.data.neo4j.support.typesafety;
import org.springframework.data.neo4j.support.ParameterCheck;
/**
* The type safety policy describes how repositories behave when loading entities from Neo4j.
*
* @author spaetzold
*/
public class TypeSafetyPolicy {
private final TypeSafetyOption option;
/**
* Creates a new instance of a type safety policy with the desired type safety option.
*
* @param option the type safety option
*/
public TypeSafetyPolicy(TypeSafetyOption option) {
ParameterCheck.notNull(option, "option");
this.option = option;
}
/**
* Creates a new instance of a type safety policy with no type safety enabled.
*/
public TypeSafetyPolicy() {
this.option = TypeSafetyOption.NONE;
}
/**
* Indicates if type safety is enabled in the system.
*
* @return true if type safety is enabled, else false
*/
public boolean isTypeSafetyEnabled() {
return option != TypeSafetyOption.NONE;
}
/**
* Gets the currently defined system type safety option.
*
* @return the type safety option
*/
public TypeSafetyOption getTypeSafetyOption() {
return option;
}
}

View File

@@ -0,0 +1,67 @@
/**
* 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.neo4j.support;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.NotFoundException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.neo4j.mapping.InvalidEntityTypeException;
import org.springframework.data.neo4j.model.Group;
import org.springframework.data.neo4j.model.Person;
import org.springframework.data.neo4j.template.Neo4jOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author spaetzold
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:template-config-context-with-type-safety-throwing-exception.xml"})
public class ExceptionThrowingTypeSafetyNeo4jTemplateTests extends EntityTestBase {
private Neo4jOperations neo4jOperations;
@Before
public void setUp() throws Exception {
createTeam();
neo4jOperations = template;
}
@Test(expected = InvalidEntityTypeException.class)
@Transactional
public void testFindOneWithWrongTypeThrowsInvalidEntityTypeException() throws Exception {
neo4jOperations.findOne(testTeam.michael.getId(), Group.class);
}
@Test
@Transactional
public void testFindOneWithRightTypeReturnsPerson() throws Exception {
final Person found = neo4jOperations.findOne(testTeam.michael.getId(), Person.class);
assertNotNull(found);
}
@Test(expected = DataRetrievalFailureException.class)
@Transactional
public void testFindOneWithNonExistingIdThrowsDataRetrievalFailureException() throws Exception {
neo4jOperations.findOne(Long.MAX_VALUE, Person.class);
}
}

View File

@@ -0,0 +1,68 @@
/**
* 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.neo4j.support;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.NotFoundException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.neo4j.mapping.InvalidEntityTypeException;
import org.springframework.data.neo4j.model.Group;
import org.springframework.data.neo4j.model.Person;
import org.springframework.data.neo4j.template.Neo4jOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author spaetzold
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:template-config-context-with-type-safety-returning-null.xml"})
public class NullReturningTypeSafetyNeo4jTemplateTests extends EntityTestBase {
private Neo4jOperations neo4jOperations;
@Before
public void setUp() throws Exception {
createTeam();
neo4jOperations = template;
}
@Test
@Transactional
public void testFindOneWithWrongTypeReturnsNull() throws Exception {
final Group found = neo4jOperations.findOne(testTeam.michael.getId(), Group.class);
assertNull(found);
}
@Test
@Transactional
public void testFindOneWithRightTypeReturnsPerson() throws Exception {
final Person found = neo4jOperations.findOne(testTeam.michael.getId(), Person.class);
assertNotNull(found);
}
@Test(expected = DataRetrievalFailureException.class)
@Transactional
public void testFindOneWithNonExistingIdThrowsDataRetrievalFailureException() throws Exception {
neo4jOperations.findOne(Long.MAX_VALUE, Person.class);
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config/>
<neo4j:config graphDatabaseService="graphDatabaseService"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.model"/>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>
<bean id="typeSafetyPolicy" class="org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy">
<constructor-arg type="org.springframework.data.neo4j.support.typesafety.TypeSafetyOption"><value>RETURNS_NULL</value></constructor-arg>
</bean>
<tx:annotation-driven mode="aspectj"/>
</beans>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config/>
<neo4j:config graphDatabaseService="graphDatabaseService"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.model"/>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>
<bean id="typeSafetyPolicy" class="org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy">
<constructor-arg type="org.springframework.data.neo4j.support.typesafety.TypeSafetyOption"><value>THROWS_EXCEPTION</value></constructor-arg>
</bean>
<tx:annotation-driven mode="aspectj"/>
</beans>