Merge pull request #131 from nickithewatt/DATAGRAPH-298

DATAGRAPH-298: GraphRepository with abstract type parameter should throw...
This commit is contained in:
Michael Hunger
2013-09-08 14:00:37 -07:00
11 changed files with 303 additions and 7 deletions

View File

@@ -28,6 +28,7 @@ import org.neo4j.kernel.impl.transaction.IllegalResourceException;
import org.neo4j.kernel.impl.transaction.LockException;
import org.springframework.dao.*;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.neo4j.mapping.InvalidEntityTypeException;
/**
* @author mh
@@ -40,6 +41,9 @@ public class Neo4jExceptionTranslator implements PersistenceExceptionTranslator
try {
throw ex;
} catch(IllegalArgumentException iae) {
if (iae.getCause() != null && iae.getCause() instanceof InvalidEntityTypeException) {
throw (InvalidEntityTypeException)iae.getCause();
}
throw new InvalidDataAccessApiUsageException(iae.getMessage(),iae);
} catch(DataAccessException dae) {
throw dae;

View File

@@ -18,6 +18,7 @@ package org.springframework.data.neo4j.support.mapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.neo4j.mapping.EntityInstantiator;
import org.springframework.data.neo4j.mapping.InvalidEntityTypeException;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.persistence.StateBackedCreator;
import org.springframework.data.persistence.StateProvider;
@@ -26,6 +27,7 @@ import sun.reflect.ReflectionFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
@@ -38,6 +40,7 @@ public abstract class AbstractConstructorEntityInstantiator<STATE> implements En
private final static Logger log = LoggerFactory.getLogger(EntityInstantiator.class);
private final Map<Class<?>, Boolean> invalidInstantiationCheck = new HashMap<Class<?>, Boolean>();
private final Map<Class<?>, StateBackedCreator<?, STATE>> cache = new HashMap<Class<?>, StateBackedCreator<?, STATE>>();
@SuppressWarnings("unchecked")
@@ -56,15 +59,30 @@ public abstract class AbstractConstructorEntityInstantiator<STATE> implements En
return creator.create(n, c);
}
} catch (IllegalArgumentException e) {
throw e;
throw e;
} catch (InvocationTargetException e) {
throw new IllegalArgumentException(e.getTargetException());
} catch (InstantiationException e) {
if (isAbstractOrInterface(c)) {
// This is the same exception that is being used in the TypeSafetyPolicy
throw new InvalidEntityTypeException("Unable to legally create entity : abstract/interface class specified : " + c);
}
throw new IllegalArgumentException(e);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
public void setInstantiators(
protected boolean isAbstractOrInterface(Class c) {
Boolean result = invalidInstantiationCheck.get(c);
if (result == null) {
result = Modifier.isAbstract(c.getModifiers()) || Modifier.isInterface(c.getModifiers());
invalidInstantiationCheck.put(c, result);
}
return result;
}
public void setInstantiators(
Map<Class<?>, StateBackedCreator<?, STATE>> instantiators) {
this.cache.putAll(instantiators);
}

View File

@@ -118,7 +118,7 @@ public class Neo4jEntityConverterImpl<T,S extends PropertyContainer> implements
private <R extends T> boolean storedAndRequestedTypesMatch(Class<R> requestedType, S source) {
TypeInformation<?> storedType = typeMapper.readType(source);
return storedType.getType().isAssignableFrom(requestedType);
return requestedType.isAssignableFrom(storedType.getType());
}
private <R extends T> void cascadeFetch(Neo4jPersistentEntityImpl<R> persistentEntity, final BeanWrapper<Neo4jPersistentEntity<R>, R> wrapper, final MappingPolicy policy, final Neo4jTemplate template) {

View File

@@ -0,0 +1,24 @@
package org.springframework.data.neo4j.model;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.NodeEntity;
/**
*
*/
@NodeEntity
public abstract class AbstractNodeEntity {
@GraphId
public Long id;
public String name;
public AbstractNodeEntity() {
this(null);
}
public AbstractNodeEntity(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,30 @@
package org.springframework.data.neo4j.model;
/**
* A concrete version (1) of AbstractNodeEntity
*/
public class Concrete1NodeEntity extends AbstractNodeEntity {
public Concrete1NodeEntity() {
super();
}
public Concrete1NodeEntity(String name) {
super(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Concrete1NodeEntity otherNode = (Concrete1NodeEntity) o;
if (id == null) return super.equals(o);
return id.equals(otherNode.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : super.hashCode();
}
}

View File

@@ -0,0 +1,31 @@
package org.springframework.data.neo4j.model;
/**
* A concrete version (2) of AbstractNodeEntity
*/
public class Concrete2NodeEntity extends AbstractNodeEntity {
public Concrete2NodeEntity() {
super();
}
public Concrete2NodeEntity(String name) {
super(name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Concrete2NodeEntity otherNode = (Concrete2NodeEntity) o;
if (id == null) return super.equals(o);
return id.equals(otherNode.id);
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : super.hashCode();
}
}

View File

@@ -0,0 +1,99 @@
/**
* 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.repository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.mapping.InvalidEntityTypeException;
import org.springframework.data.neo4j.model.AbstractNodeEntity;
import org.springframework.data.neo4j.model.Concrete1NodeEntity;
import org.springframework.data.neo4j.model.Concrete2NodeEntity;
import org.springframework.data.neo4j.model.Person;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.node.Neo4jHelper;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
/**
* Tests for repositories which are defined against an Abstract Entity.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class AbstractEntityBasedGraphRepositoryTests {
protected final Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private Neo4jTemplate neo4jTemplate;
@Autowired
private PersonRepository personRepository;
@Autowired
AbstractNodeEntityRepository abstractNodeEntityRepository;
@BeforeTransaction
public void cleanDb() {
Neo4jHelper.cleanDb(neo4jTemplate);
}
@Transactional
@Test(expected = InvalidEntityTypeException.class) // DATAGRAPH-298
public void testInvalidEntityLoadAttemptThroughAbstractRepoThrowsAppropriateException() {
Person person = personRepository.save(new Person("someone",30));
// We are trying to load a Person Node Entity, using a completely different
// and abstract defined Node entity - this should fail
AbstractNodeEntity shouldNotWork = abstractNodeEntityRepository.findOne(person.getId());
}
@Test
@Transactional // DATAGRAPH-298
public void testConcreteEntityLoadedThroughAbstractRepoLoadsCorrectType() {
Concrete1NodeEntity origConcrete1 = new Concrete1NodeEntity("concrete1A");
Concrete2NodeEntity origConcrete2 = new Concrete2NodeEntity("concrete2A");
abstractNodeEntityRepository.save(origConcrete1);
abstractNodeEntityRepository.save(origConcrete2);
Concrete1NodeEntity loadedConcrete1 = (Concrete1NodeEntity)abstractNodeEntityRepository.findOne(origConcrete1.id);
assertNotNull(loadedConcrete1);
assertThat(loadedConcrete1, is(origConcrete1));
Concrete2NodeEntity loadedConcrete2 = (Concrete2NodeEntity)abstractNodeEntityRepository.findOne(origConcrete2.id);
assertNotNull(loadedConcrete2);
assertThat(loadedConcrete2, is(origConcrete2));
}
}

View File

@@ -0,0 +1,11 @@
package org.springframework.data.neo4j.repository;
import org.springframework.data.neo4j.model.AbstractNodeEntity;
import org.springframework.data.neo4j.repository.GraphRepository;
/**
* This Repository has specifically been created against the abstract node entity
* class, to be able to test loading concrete specific node entities.
*/
public interface AbstractNodeEntityRepository extends GraphRepository<AbstractNodeEntity> {
}

View File

@@ -21,13 +21,13 @@ 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.model.*;
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.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -64,4 +64,36 @@ public class ExceptionThrowingTypeSafetyNeo4jTemplateTests extends EntityTestBas
public void testFindOneWithNonExistingIdThrowsDataRetrievalFailureException() throws Exception {
neo4jOperations.findOne(Long.MAX_VALUE, Person.class);
}
@Test(expected = InvalidEntityTypeException.class)
@Transactional
public void testFindOneWithAbstractWrongTypeThrowsInvalidEntityTypeException() throws Exception {
neo4jOperations.findOne(testTeam.michael.getId(), AbstractNodeEntity.class);
}
@Test
@Transactional
public void testFindOneWithConcreteEntityAndConcreteTypeReturnsConcreteEntity() throws Exception {
AbstractNodeEntity origConcrete1NodeEntity = neo4jOperations.save(new Concrete1NodeEntity("concrete1"));
AbstractNodeEntity readConcrete1NodeEntity = neo4jOperations.findOne(origConcrete1NodeEntity.id, Concrete1NodeEntity.class);
assertNotNull(readConcrete1NodeEntity);
assertEquals(origConcrete1NodeEntity,readConcrete1NodeEntity);
}
@Test
@Transactional
public void testFindOneWithConcreteEntityAndAbstractTypeReturnsConcreteEntity() throws Exception {
Concrete1NodeEntity origConcrete1NodeEntity = neo4jOperations.save(new Concrete1NodeEntity("concrete1"));
Concrete1NodeEntity readConcrete1NodeEntity = (Concrete1NodeEntity)neo4jOperations.findOne(origConcrete1NodeEntity.id, AbstractNodeEntity.class);
assertNotNull(readConcrete1NodeEntity);
assertEquals(origConcrete1NodeEntity,readConcrete1NodeEntity);
}
@Test(expected = InvalidEntityTypeException.class)
@Transactional
public void testFindOneWithDifferentConcreteEntitiesThrowsInvalidEntityTypeException() throws Exception {
Concrete2NodeEntity origConcrete2NodeEntity = neo4jOperations.save(new Concrete2NodeEntity("concrete2"));
AbstractNodeEntity readConcrete2NodeEntity = neo4jOperations.findOne(origConcrete2NodeEntity.id, Concrete1NodeEntity.class);
}
}

View File

@@ -21,13 +21,13 @@ 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.model.*;
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.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@@ -65,4 +65,38 @@ public class NullReturningTypeSafetyNeo4jTemplateTests extends EntityTestBase {
public void testFindOneWithNonExistingIdThrowsDataRetrievalFailureException() throws Exception {
neo4jOperations.findOne(Long.MAX_VALUE, Person.class);
}
@Test
@Transactional
public void testFindOneWithAbstractWrongTypeReturnsNull() throws Exception {
AbstractNodeEntity entity = neo4jOperations.findOne(testTeam.michael.getId(), AbstractNodeEntity.class);
assertNull(entity);
}
@Test
@Transactional
public void testFindOneWithConcreteEntityAndConcreteTypeReturnsConcreteEntity() throws Exception {
AbstractNodeEntity origConcrete1NodeEntity = neo4jOperations.save(new Concrete1NodeEntity("concrete1"));
AbstractNodeEntity readConcrete1NodeEntity = neo4jOperations.findOne(origConcrete1NodeEntity.id, Concrete1NodeEntity.class);
assertNotNull(readConcrete1NodeEntity);
assertEquals(origConcrete1NodeEntity,readConcrete1NodeEntity);
}
@Test
@Transactional
public void testFindOneWithConcreteEntityAndAbstractTypeReturnsConcreteEntity() throws Exception {
Concrete1NodeEntity origConcrete1NodeEntity = neo4jOperations.save(new Concrete1NodeEntity("concrete1"));
Concrete1NodeEntity readConcrete1NodeEntity = (Concrete1NodeEntity)neo4jOperations.findOne(origConcrete1NodeEntity.id, AbstractNodeEntity.class);
assertNotNull(readConcrete1NodeEntity);
assertEquals(origConcrete1NodeEntity,readConcrete1NodeEntity);
}
@Test
@Transactional
public void testFindOneWithDifferentConcreteEntitiesReturnsNull() throws Exception {
Concrete2NodeEntity origConcrete2NodeEntity = neo4jOperations.save(new Concrete2NodeEntity("concrete2"));
AbstractNodeEntity readConcrete2NodeEntity = neo4jOperations.findOne(origConcrete2NodeEntity.id, Concrete1NodeEntity.class);
assertNull(readConcrete2NodeEntity);
}
}

View File

@@ -0,0 +1,13 @@
<?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"
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">
<context:annotation-config/>
<neo4j:config graphDatabaseService="graphDatabaseService"/>
<neo4j:repositories base-package="org.springframework.data.neo4j.repository"/>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>
</beans>