added virtual field traversal
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package org.springframework.datastore.graph.api;
|
||||
|
||||
import org.neo4j.graphdb.traversal.TraversalDescription;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 15.09.2010
|
||||
*/
|
||||
public interface FieldTraversalDescriptionBuilder {
|
||||
TraversalDescription build(NodeBacked start, Field field);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.springframework.datastore.graph.api;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 15.09.2010
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface GraphEntityTraversal {
|
||||
Class<? extends FieldTraversalDescriptionBuilder> traversalBuilder() default FieldTraversalDescriptionBuilder.class;
|
||||
Class<? extends NodeBacked> elementClass() default NodeBacked.class;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package org.springframework.datastore.graph.neo4j.fieldaccess;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.NotInTransactionException;
|
||||
import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
import org.springframework.datastore.graph.api.NodeBacked;
|
||||
import org.springframework.datastore.graph.neo4j.support.GraphDatabaseContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 12.09.2010
|
||||
*/
|
||||
public class DefaultEntityStateAccessors<ENTITY extends NodeBacked, STATE> implements EntityStateAccessors<ENTITY> {
|
||||
private final STATE underlyingState;
|
||||
private final ENTITY entity;
|
||||
private final Class<? extends ENTITY> type;
|
||||
private final GraphDatabaseContext graphDatabaseContext;
|
||||
private final Map<Field,FieldAccessor<ENTITY,?>> fieldAccessors=new HashMap<Field, FieldAccessor<ENTITY,?>>();
|
||||
private final Map<Field,List<FieldAccessListener<ENTITY,?>>> fieldAccessorListeners=new HashMap<Field, List<FieldAccessListener<ENTITY,?>>>();
|
||||
private Node node;
|
||||
private final static Log log= LogFactory.getLog(DefaultEntityStateAccessors.class);
|
||||
|
||||
|
||||
public DefaultEntityStateAccessors(final STATE underlyingState, final ENTITY entity, final Class<? extends ENTITY> type, final GraphDatabaseContext graphDatabaseContext) {
|
||||
this.underlyingState = underlyingState;
|
||||
this.entity = entity;
|
||||
this.type = type;
|
||||
this.graphDatabaseContext = graphDatabaseContext;
|
||||
createAccessorsAndListeners(type, graphDatabaseContext);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void createAndAssignNode() {
|
||||
try {
|
||||
final Node node=graphDatabaseContext.createNode();
|
||||
setNode(node);
|
||||
entity.setUnderlyingNode(node);
|
||||
log.info("User-defined constructor called on class " + entity.getClass() + "; created Node [" + entity.getUnderlyingNode() +"]; Updating metamodel");
|
||||
graphDatabaseContext.postEntityCreation(entity);
|
||||
} catch(NotInTransactionException e) {
|
||||
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ENTITY getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphDatabaseContext getGraphDatabaseContext() {
|
||||
return graphDatabaseContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNode(final Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
private void createAccessorsAndListeners(final Class<? extends ENTITY> type, final GraphDatabaseContext graphDatabaseContext) {
|
||||
final DelegatingFieldAccessorFactory fieldAccessorFactory = new DelegatingFieldAccessorFactory(graphDatabaseContext);
|
||||
ReflectionUtils.doWithFields(type, new ReflectionUtils.FieldCallback() {
|
||||
public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException {
|
||||
fieldAccessors.put(field, fieldAccessorFactory.forField(field));
|
||||
fieldAccessorListeners.put(field, fieldAccessorFactory.listenersFor(field)); // TODO Bad code
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(final Field field) {
|
||||
final FieldAccessor<ENTITY, ?> accessor = accessorFor(field);
|
||||
if (accessor == null) return null;
|
||||
else return accessor.getValue(entity);
|
||||
}
|
||||
@Override
|
||||
public Object setValue(final Field field, final Object newVal) {
|
||||
final FieldAccessor<ENTITY, ?> accessor = accessorFor(field);
|
||||
final Object result=accessor!=null ? accessor.setValue(entity, newVal) : newVal;
|
||||
notifyListeners(field, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private FieldAccessor<ENTITY, ?> accessorFor(final Field field) {
|
||||
return fieldAccessors.get(field);
|
||||
}
|
||||
|
||||
private void notifyListeners(final Field field, final Object result) {
|
||||
if (!fieldAccessorListeners.containsKey(field) || fieldAccessorListeners.get(field) == null) return;
|
||||
|
||||
for (final FieldAccessListener<ENTITY, ?> listener : fieldAccessorListeners.get(field)) {
|
||||
listener.valueChanged(entity, null, result); // todo oldValue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ public class DelegatingFieldAccessorFactory<T> implements FieldAccessorFactory<T
|
||||
SingleRelationshipFieldAccessor.factory(),
|
||||
OneToNRelationshipFieldAccessor.factory(),
|
||||
ReadOnlyOneToNRelationshipFieldAccessor.factory(),
|
||||
TraversalFieldAccessor.factory(),
|
||||
OneToNRelationshipEntityFieldAccessor.factory()
|
||||
);
|
||||
final Collection<FieldAccessorListenerFactory<?>> fieldAccessorListenerFactories = Arrays.<FieldAccessorListenerFactory<?>>asList(
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package org.springframework.datastore.graph.neo4j.fieldaccess;
|
||||
|
||||
import org.neo4j.graphdb.traversal.TraversalDescription;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Configurable;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.datastore.graph.api.FieldTraversalDescriptionBuilder;
|
||||
import org.springframework.datastore.graph.api.GraphEntityTraversal;
|
||||
import org.springframework.datastore.graph.api.NodeBacked;
|
||||
import org.springframework.datastore.graph.neo4j.finder.Finder;
|
||||
import org.springframework.datastore.graph.neo4j.finder.FinderFactory;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.springframework.datastore.graph.neo4j.fieldaccess.DoReturn.doReturn;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 12.09.2010
|
||||
*/
|
||||
public class TraversalFieldAccessor implements FieldAccessor<NodeBacked, Object> {
|
||||
protected final Field field;
|
||||
private final FinderFactory finderFactory;
|
||||
private final FieldTraversalDescriptionBuilder fieldTraversalDescriptionBuilder;
|
||||
private Class<? extends NodeBacked> target;
|
||||
|
||||
public TraversalFieldAccessor(final Field field, FinderFactory finderFactory) {
|
||||
this.field = field;
|
||||
this.finderFactory = finderFactory;
|
||||
final GraphEntityTraversal graphEntityTraversal = field.getAnnotation(GraphEntityTraversal.class);
|
||||
this.target = graphEntityTraversal.elementClass();
|
||||
this.fieldTraversalDescriptionBuilder = createTraversalDescription(graphEntityTraversal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setValue(final NodeBacked nodeBacked, final Object newVal) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot set readonly traversal description field " + field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(final NodeBacked nodeBacked) {
|
||||
final Finder<? extends NodeBacked> finder = finderFactory.getFinderForClass(target);
|
||||
final TraversalDescription traversalDescription = fieldTraversalDescriptionBuilder.build(nodeBacked,field);
|
||||
return doReturn(finder.findAllByTraversal(nodeBacked, traversalDescription));
|
||||
}
|
||||
|
||||
|
||||
private FieldTraversalDescriptionBuilder createTraversalDescription(final GraphEntityTraversal graphEntityTraversal) {
|
||||
try {
|
||||
final Class<? extends FieldTraversalDescriptionBuilder> traversalDescriptionClass = graphEntityTraversal.traversalBuilder();
|
||||
final Constructor<? extends FieldTraversalDescriptionBuilder> constructor = traversalDescriptionClass.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error creating TraversalDescription from " + field,e);
|
||||
}
|
||||
}
|
||||
|
||||
public static FieldAccessorFactory<NodeBacked> factory() {
|
||||
return new TraversalFieldAccessorFactory();
|
||||
}
|
||||
|
||||
@Configurable
|
||||
private static class TraversalFieldAccessorFactory implements FieldAccessorFactory<NodeBacked> {
|
||||
@Autowired
|
||||
private FinderFactory finderFactory;
|
||||
|
||||
@Override
|
||||
public boolean accept(final Field f) {
|
||||
final GraphEntityTraversal graphEntityTraversal = f.getAnnotation(GraphEntityTraversal.class);
|
||||
return graphEntityTraversal != null
|
||||
&& graphEntityTraversal.traversalBuilder() != TraversalDescription.class
|
||||
&& f.getType().equals(Iterable.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FieldAccessor<NodeBacked, ?> forField(final Field field) {
|
||||
return new TraversalFieldAccessor(field, finderFactory);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,25 @@
|
||||
package org.springframework.datastore.graph.neo4j;
|
||||
|
||||
import org.springframework.datastore.graph.api.Direction;
|
||||
import org.springframework.datastore.graph.api.GraphEntity;
|
||||
import org.springframework.datastore.graph.api.GraphEntityRelationship;
|
||||
import org.neo4j.graphdb.DynamicRelationshipType;
|
||||
import org.neo4j.graphdb.traversal.TraversalDescription;
|
||||
import org.neo4j.kernel.Traversal;
|
||||
import org.neo4j.kernel.impl.traversal.TraversalDescriptionImpl;
|
||||
import org.springframework.datastore.graph.api.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
@GraphEntity(fullIndex = true)
|
||||
public class Group {
|
||||
|
||||
@GraphEntityRelationship(type = "persons", direction = Direction.OUTGOING, elementClass = Person.class)
|
||||
private Collection<Person> persons;
|
||||
|
||||
@GraphEntityRelationship(type = "persons", elementClass = Person.class)
|
||||
private Iterable<Person> readOnlyPersons;
|
||||
@GraphEntityRelationship(type = "persons", direction = Direction.OUTGOING, elementClass = Person.class)
|
||||
private Collection<Person> persons;
|
||||
|
||||
@GraphEntityRelationship(type = "persons", elementClass = Person.class)
|
||||
private Iterable<Person> readOnlyPersons;
|
||||
|
||||
@GraphEntityTraversal(traversalBuilder = PeopleTraversalBuilder.class, elementClass = Person.class)
|
||||
private Iterable<Person> people;
|
||||
|
||||
private String name;
|
||||
|
||||
@@ -26,23 +32,36 @@ public class Group {
|
||||
}
|
||||
|
||||
public void setPersons(Collection<Person> persons) {
|
||||
this.persons = persons;
|
||||
}
|
||||
this.persons = persons;
|
||||
}
|
||||
|
||||
public void addPerson(Person person) {
|
||||
persons.add(person);
|
||||
}
|
||||
public void addPerson(Person person) {
|
||||
persons.add(person);
|
||||
}
|
||||
|
||||
public Collection<Person> getPersons() {
|
||||
return persons;
|
||||
}
|
||||
|
||||
public Iterable<Person> getReadOnlyPersons() {
|
||||
return readOnlyPersons;
|
||||
}
|
||||
public Collection<Person> getPersons() {
|
||||
return persons;
|
||||
}
|
||||
|
||||
public void setReadOnlyPersons(Iterable<Person> p) {
|
||||
readOnlyPersons = p;
|
||||
}
|
||||
|
||||
public Iterable<Person> getReadOnlyPersons() {
|
||||
return readOnlyPersons;
|
||||
}
|
||||
|
||||
public void setReadOnlyPersons(Iterable<Person> p) {
|
||||
readOnlyPersons = p;
|
||||
}
|
||||
|
||||
public Iterable<Person> getPeople() {
|
||||
return people;
|
||||
}
|
||||
|
||||
private static class PeopleTraversalBuilder implements FieldTraversalDescriptionBuilder {
|
||||
@Override
|
||||
public TraversalDescription build(NodeBacked start, Field field) {
|
||||
return new TraversalDescriptionImpl()
|
||||
.relationships(DynamicRelationshipType.withName("persons"))
|
||||
.filter(Traversal.returnAllButStartNode());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +446,22 @@ public class Neo4jGraphPersistenceTest {
|
||||
}
|
||||
Assert.assertEquals(Collections.singleton(p),found);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testTraverseFieldFromGroupToPeople() {
|
||||
Person p = new Person("Michael", 35);
|
||||
Group group = new Group();
|
||||
group.setName("dev");
|
||||
group.addPerson(p);
|
||||
Iterable<Person> people = group.getPeople();
|
||||
final HashSet<Person> found = new HashSet<Person>();
|
||||
for (Person person : people) {
|
||||
found.add(person);
|
||||
}
|
||||
Assert.assertEquals(Collections.singleton(p),found);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testTraverseFromGroupToPeopleWithFinder() {
|
||||
|
||||
Reference in New Issue
Block a user