diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java index 34cc4f8f2..3501fd192 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java @@ -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.typerepresentation.ClassValueTypeInformationMapper; 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.transaction.PlatformTransactionManager; import javax.validation.Validator; @@ -117,6 +118,7 @@ public abstract class Neo4jConfiguration { factoryBean.setTransactionManager(neo4jTransactionManager()); factoryBean.setGraphDatabase(graphDatabase()); factoryBean.setIsNewStrategyFactory(isNewStrategyFactory()); + factoryBean.setTypeSafetyPolicy(typeSafetyPolicy()); factoryBean.setIndexProvider(indexProvider()); @@ -269,6 +271,11 @@ public abstract class Neo4jConfiguration { return new IndexProviderImpl(neo4jMappingContext(), graphDatabase()); } + @Bean + public TypeSafetyPolicy typeSafetyPolicy() throws Exception { + return new TypeSafetyPolicy(); + } + public Set> getInitialEntitySet() { return initialEntitySet; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java index 6fdfcc415..698402d6a 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java @@ -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.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; +import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy; import org.springframework.transaction.PlatformTransactionManager; import javax.validation.Validator; @@ -68,4 +69,6 @@ public interface Infrastructure { TypeRepresentationStrategy getNodeTypeRepresentationStrategy(); TypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(); + + TypeSafetyPolicy getTypeSafetyPolicy(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java index dc7c23c27..dd420fdb9 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java @@ -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.typerepresentation.TypeRepresentationStrategies; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory; +import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy; import org.springframework.transaction.PlatformTransactionManager; import javax.validation.Validator; @@ -59,8 +60,9 @@ public class MappingInfrastructure implements Infrastructure { private final IndexProvider indexProvider; private final GraphDatabaseService graphDatabaseService; 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 relationshipTypeRepresentationStrategy, TypeRepresentationStrategy 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 relationshipTypeRepresentationStrategy, TypeRepresentationStrategy nodeTypeRepresentationStrategy, Validator validator, ConversionService conversionService, TypeSafetyPolicy typeSafetyPolicy) { this.graphDatabase = graphDatabase; this.graphDatabaseService = graphDatabaseService; this.indexProvider = indexProvider; @@ -76,6 +78,7 @@ public class MappingInfrastructure implements Infrastructure { this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy; this.validator = validator; this.conversionService = conversionService; + this.typeSafetyPolicy = typeSafetyPolicy; } @Override @@ -147,4 +150,9 @@ public class MappingInfrastructure implements Infrastructure { public TypeRepresentationStrategy getRelationshipTypeRepresentationStrategy() { return relationshipTypeRepresentationStrategy; } + + @Override + public TypeSafetyPolicy getTypeSafetyPolicy() { + return typeSafetyPolicy; + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java index 3f9a2ad73..d25adf451 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java @@ -47,6 +47,7 @@ import org.springframework.data.neo4j.support.relationship.RelationshipEntityIns import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory; import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies; 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.transaction.PlatformTransactionManager; import org.springframework.transaction.jta.JtaTransactionManager; @@ -80,6 +81,7 @@ public class MappingInfrastructureFactoryBean implements FactoryBean implements // retrieve meta-information about the type @SuppressWarnings("unchecked") final Neo4jPersistentEntityImpl persistentEntity = (Neo4jPersistentEntityImpl) mappingContext.getPersistentEntity(targetType); - if (mappingPolicy==null) { - mappingPolicy = persistentEntity.getMappingPolicy(); + // 4) check type safety + 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); - // 5) connect state + // 6) connect state entityStateHandler.setPersistentState(createdEntity,source); if (persistentEntity.isManaged()) return createdEntity; @@ -104,6 +116,11 @@ public class Neo4jEntityConverterImpl implements return entity; } + private boolean storedAndRequestedTypesMatch(Class requestedType, S source) { + TypeInformation storedType = typeMapper.readType(source); + return storedType.getType().isAssignableFrom(requestedType); + } + private void cascadeFetch(Neo4jPersistentEntityImpl persistentEntity, final BeanWrapper, R> wrapper, final MappingPolicy policy, final Neo4jTemplate template) { persistentEntity.doWithAssociations(new AssociationHandler() { @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typesafety/TypeSafetyOption.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typesafety/TypeSafetyOption.java new file mode 100644 index 000000000..83bb44a11 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typesafety/TypeSafetyOption.java @@ -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 + +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typesafety/TypeSafetyPolicy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typesafety/TypeSafetyPolicy.java new file mode 100644 index 000000000..c884a0410 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typesafety/TypeSafetyPolicy.java @@ -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; + } +} + diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/ExceptionThrowingTypeSafetyNeo4jTemplateTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/ExceptionThrowingTypeSafetyNeo4jTemplateTests.java new file mode 100644 index 000000000..28c4125e0 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/ExceptionThrowingTypeSafetyNeo4jTemplateTests.java @@ -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); + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/NullReturningTypeSafetyNeo4jTemplateTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/NullReturningTypeSafetyNeo4jTemplateTests.java new file mode 100644 index 000000000..fcd6afa75 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/NullReturningTypeSafetyNeo4jTemplateTests.java @@ -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); + } +} diff --git a/spring-data-neo4j/src/test/resources/template-config-context-with-type-safety-returning-null.xml b/spring-data-neo4j/src/test/resources/template-config-context-with-type-safety-returning-null.xml new file mode 100644 index 000000000..1c1297e54 --- /dev/null +++ b/spring-data-neo4j/src/test/resources/template-config-context-with-type-safety-returning-null.xml @@ -0,0 +1,19 @@ + + + + + + + + + RETURNS_NULL + + + \ No newline at end of file diff --git a/spring-data-neo4j/src/test/resources/template-config-context-with-type-safety-throwing-exception.xml b/spring-data-neo4j/src/test/resources/template-config-context-with-type-safety-throwing-exception.xml new file mode 100644 index 000000000..40e472a58 --- /dev/null +++ b/spring-data-neo4j/src/test/resources/template-config-context-with-type-safety-throwing-exception.xml @@ -0,0 +1,19 @@ + + + + + + + + + THROWS_EXCEPTION + + + \ No newline at end of file