Refactorings of RelatedTo(Via)FieldAccessors

DATAGRAPH-182 allow @RelatedTo on Single RelationshipEntity fields
DATAGRAPH-202 provide a getRelationshipsBetween() method in Neo4jTemplate
This commit is contained in:
Michael Hunger
2012-02-28 14:16:56 +01:00
parent 587f9c0637
commit 66bc7763c7
27 changed files with 812 additions and 452 deletions

View File

@@ -64,6 +64,8 @@ public class Group {
@Query("start n=node({self}) match n-[:persons]->() return count(*)")
private Long memberCount;
@RelatedToVia(type="mentors", direction = Direction.INCOMING)
Mentorship mentorship;
@GraphProperty
private String unindexedName;
@@ -275,4 +277,12 @@ public class Group {
public void setRolesIterable(Iterable<Role> rolesIterable) {
this.rolesIterable = rolesIterable;
}
public Mentorship getMentorship() {
return mentorship;
}
public void setMentorship(Mentorship mentorship) {
this.mentorship = mentorship;
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.aspects;
import org.springframework.data.neo4j.annotation.EndNode;
import org.springframework.data.neo4j.annotation.GraphId;
import org.springframework.data.neo4j.annotation.RelationshipEntity;
import org.springframework.data.neo4j.annotation.StartNode;
@RelationshipEntity(type = "mentors")
public class Mentorship {
@GraphId Long id;
public Long getId() {
return id;
}
public Mentorship() {
}
public Mentorship(Person mentor, Group group) {
this.mentor = mentor;
this.group = group;
}
@StartNode
private Person mentor;
@EndNode
private Group group;
public Person getMentor() {
return mentor;
}
public Group getGroup() {
return group;
}
}

View File

@@ -27,14 +27,23 @@ import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.aspects.Friendship;
import org.springframework.data.neo4j.aspects.Group;
import org.springframework.data.neo4j.aspects.Mentorship;
import org.springframework.data.neo4j.aspects.Person;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.springframework.data.neo4j.aspects.Person.persistedPerson;
@@ -254,4 +263,51 @@ public class NodeEntityRelationshipTest extends EntityTestBase {
group.setReadOnlyPersons(new HashSet<Person>());
}
@Test
@Transactional
public void testSingleRelatedToViaField() {
Group group = persist(new Group());
Person mentor = persist(new Person());
group.setMentorship(new Mentorship(mentor,group));
persist(group);
final Node node = neo4jTemplate.getPersistentState(group);
assertEquals(1,IteratorUtil.count(node.getRelationships(Direction.INCOMING,DynamicRelationshipType.withName("mentors"))));
final Group loaded = neo4jTemplate.load(node, Group.class);
assertEquals(group.getMentorship(),loaded.getMentorship());
assertEquals(group.getMentorship().getId(),loaded.getMentorship().getId());
assertEquals(mentor, group.getMentorship().getMentor());
assertEquals(group, group.getMentorship().getGroup());
}
@Test
@Transactional
public void testRemoveSingleRelatedToViaField() {
Group group = persist(new Group());
Person mentor = persist(new Person());
group.setMentorship(new Mentorship(mentor,group));
persist(group);
group.setMentorship(null);
persist(group);
final Node node = neo4jTemplate.getPersistentState(group);
assertEquals(0,IteratorUtil.count(node.getRelationships(Direction.INCOMING,DynamicRelationshipType.withName("mentors"))));
final Group loaded = neo4jTemplate.load(node, Group.class);
assertThat(loaded.getMentorship(), is(nullValue()));
}
@Test
@Transactional
public void testUpdateSingleRelatedToViaField() {
Group group = persist(new Group());
group.setMentorship(new Mentorship(persist(new Person()),group));
persist(group);
final Long firstMentorshipId = group.getMentorship().getId();
final Person mentor2 = new Person();
group.setMentorship(new Mentorship(persist(mentor2),group));
persist(group);
final Node node = neo4jTemplate.getPersistentState(group);
assertEquals(1,IteratorUtil.count(node.getRelationships(Direction.INCOMING,DynamicRelationshipType.withName("mentors"))));
final Group loaded = neo4jTemplate.load(node, Group.class);
assertFalse(loaded.getMentorship().getId().equals(firstMentorshipId));
assertEquals(mentor2, group.getMentorship().getMentor());
assertEquals(group, group.getMentorship().getGroup());
}
}

View File

@@ -146,15 +146,16 @@ public class CrossStoreNodeEntityState<ENTITY extends NodeBacked> extends Defaul
new QueryFieldAccessorFactory(template),
newPropertyFieldAccessorFactory(),
newConvertingNodePropertyFieldAccessorFactory(),
new SingleRelationshipFieldAccessorFactory(getTemplate()) {
new RelatedToSingleFieldAccessorFactory(getTemplate()) {
@Override
public boolean accept(Neo4jPersistentProperty property) {
return property.isAnnotationPresent(RelatedTo.class) && super.accept(property);
}
},
new OneToNRelationshipFieldAccessorFactory(getTemplate()),
new ReadOnlyOneToNRelationshipFieldAccessorFactory(getTemplate()),
new OneToNRelationshipEntityFieldAccessorFactory(getTemplate())
new RelatedToCollectionFieldAccessorFactory(getTemplate()),
new ReadOnlyRelatedToCollectionFieldAccessorFactory(getTemplate()),
new RelatedToViaSingleFieldAccessorFactory(getTemplate()),
new RelatedToViaCollectionFieldAccessorFactory(getTemplate())
);
}

View File

@@ -1,143 +0,0 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.*;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.util.Assert;
import java.util.HashSet;
import java.util.Set;
/**
* @author Michael Hunger
* @since 11.09.2010
*/
public abstract class AbstractNodeRelationshipFieldAccessor<STATE extends PropertyContainer,TSTATE extends PropertyContainer> implements FieldAccessor {
protected final RelationshipType type;
protected final Neo4jPersistentProperty property;
protected final Direction direction;
protected final Class<?> relatedType;
protected final Neo4jTemplate template;
public AbstractNodeRelationshipFieldAccessor(Class<?> clazz, Neo4jTemplate template, Direction direction, RelationshipType type, Neo4jPersistentProperty property) {
this.relatedType = clazz;
this.template = template;
this.direction = direction;
this.type = type;
this.property = property;
}
protected MappingPolicy updateMappingPolicy(MappingPolicy mappingPolicy) {
if (mappingPolicy !=null) return mappingPolicy;
return property.getMappingPolicy();
}
@Override
public boolean isWriteable(Object entity) {
return true;
}
protected STATE checkUnderlyingState(Object entity) {
if (entity==null) throw new IllegalStateException("Entity is null");
STATE node = getState(entity);
if (node != null) return node;
throw new IllegalStateException("Entity must have a backing Node");
}
protected void removeMissingRelationships(Node node, Set<Node> targetNodes) {
for ( Relationship relationship : node.getRelationships(type, direction) ) {
if (!targetNodes.remove(relationship.getOtherNode(node)))
relationship.delete();
}
}
protected void createAddedRelationships(Node node, Set<Node> targetNodes) {
for (Node targetNode : targetNodes) {
createSingleRelationship(node,targetNode);
}
}
// adding cascade
@SuppressWarnings("unchecked")
protected Set<Node> createSetOfTargetNodes(Object newVal) {
if (!(newVal instanceof Set)) {
throw new IllegalArgumentException("New value must be a Set, was: " + newVal.getClass());
}
Set<Node> nodes=new HashSet<Node>();
for (Object value : (Set<Object>) newVal) {
if (!relatedType.isInstance(value)) {
throw new IllegalArgumentException("New value elements must be "+relatedType);
}
nodes.add((Node)getOrCreateState(value));
}
return nodes;
}
protected STATE getOrCreateState(Object value) {
final STATE state = getState(value);
if (state != null) return state;
final Object saved = template.save(value);
final STATE newState = getState(saved);
Assert.notNull(newState);
return newState;
}
protected <T> ManagedFieldAccessorSet<T> createManagedSet(Object entity, Set<T> result, MappingPolicy mappingPolicy) {
return new ManagedFieldAccessorSet<T>(entity, result, property, template,this, mappingPolicy);
}
protected Set<Object> createEntitySetFromRelationshipEndNodes(Object entity, final MappingPolicy mappingPolicy) {
final Iterable<TSTATE> nodes = getStatesFromEntity(entity);
final Set<Object> result = new HashSet<Object>();
for (final TSTATE otherNode : nodes) {
Object target= template.createEntityFromState(otherNode, relatedType, mappingPolicy);
result.add(target);
}
return result;
}
@SuppressWarnings("unchecked")
protected void createSingleRelationship(Node start, Node end) {
if (end==null) return;
switch(direction) {
case OUTGOING :
case BOTH : { // TODO both should actually check in both directions, perhaps have the obtain method get the direction instead and figure out what to do itself
obtainSingleRelationship(start, end);
break;
}
case INCOMING :
obtainSingleRelationship(end, start);
break;
default : throw new InvalidDataAccessApiUsageException("invalid direction " + direction);
}
}
public Object getDefaultValue() {
return null;
}
protected abstract Relationship obtainSingleRelationship(Node start, Node end);
protected abstract Iterable<TSTATE> getStatesFromEntity(Object entity);
protected abstract STATE getState(Object entity);
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.neo4j.fieldaccess;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.helpers.collection.IterableWrapper;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.support.Neo4jTemplate;
/**
@@ -26,16 +27,18 @@ import org.springframework.data.neo4j.support.Neo4jTemplate;
public class GraphBackedEntityIterableWrapper<STATE extends PropertyContainer, ENTITY> extends IterableWrapper<ENTITY, STATE> {
private final Class<ENTITY> targetType;
private final Neo4jTemplate template;
private final MappingPolicy mappingPolicy;
public GraphBackedEntityIterableWrapper(Iterable<STATE> iterable, Class<ENTITY> targetType, final Neo4jTemplate template) {
super(iterable);
this.targetType = targetType;
this.template = template;
mappingPolicy = this.template.getMappingPolicy(this.targetType);
}
@Override
protected ENTITY underlyingObjectToObject(STATE s) {
return template.createEntityFromState(s, targetType, template.getMappingPolicy(targetType));
return template.createEntityFromState(s, targetType, mappingPolicy);
}
public static <S extends PropertyContainer, E> GraphBackedEntityIterableWrapper<S, E> create(

View File

@@ -49,7 +49,11 @@ public class ManagedFieldAccessorSet<T> extends AbstractSet<T> {
this.mappingPolicy = mappingPolicy;
}
@Override
public static <T> ManagedFieldAccessorSet<T> create(Object entity, Set<T> result, MappingPolicy mappingPolicy, final Neo4jPersistentProperty property, final Neo4jTemplate template, final FieldAccessor fieldAccessor) {
return new ManagedFieldAccessorSet<T>(entity, result, property, template, fieldAccessor, mappingPolicy);
}
@Override
public Iterator<T> iterator() {
final Iterator<T> iterator = delegate.iterator();
return new Iterator<T>() {

View File

@@ -52,10 +52,11 @@ public class NodeDelegatingFieldAccessorFactory extends DelegatingFieldAccessorF
new QueryFieldAccessorFactory(template),
new PropertyFieldAccessorFactory(template),
new ConvertingNodePropertyFieldAccessorFactory(template),
new SingleRelationshipFieldAccessorFactory(template),
new OneToNRelationshipFieldAccessorFactory(template),
new ReadOnlyOneToNRelationshipFieldAccessorFactory(template),
new OneToNRelationshipEntityFieldAccessorFactory(template),
new RelatedToSingleFieldAccessorFactory(template),
new RelatedToCollectionFieldAccessorFactory(template),
new ReadOnlyRelatedToCollectionFieldAccessorFactory(template),
new RelatedToViaCollectionFieldAccessorFactory(template),
new RelatedToViaSingleFieldAccessorFactory(template),
new DynamicPropertiesFieldAccessorFactory(template)
);
}

View File

@@ -1,33 +0,0 @@
/**
* 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.fieldaccess;
import org.springframework.data.neo4j.support.Neo4jTemplate;
/**
* @author Michael Hunger
* @since 12.09.2010
*/
public abstract class NodeRelationshipFieldAccessorFactory implements FieldAccessorFactory {
protected Neo4jTemplate template;
public NodeRelationshipFieldAccessorFactory(Neo4jTemplate template) {
this.template = template;
}
}

View File

@@ -1,62 +0,0 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import java.util.HashSet;
import java.util.Set;
/**
* @author Michael Hunger
* @since 12.09.2010
*/
public abstract class NodeToNodesRelationshipFieldAccessor extends AbstractNodeRelationshipFieldAccessor<Node, Node> {
public NodeToNodesRelationshipFieldAccessor(final Class<?> clazz, final Neo4jTemplate template, final Direction direction, final RelationshipType type, Neo4jPersistentProperty property) {
super(clazz, template, direction, type,property);
}
@Override
protected Relationship obtainSingleRelationship(final Node start, final Node end) {
final Iterable<Relationship> existingRelationships = start.getRelationships(type, direction);
for (final Relationship existingRelationship : existingRelationships) {
if (existingRelationship!=null && existingRelationship.getOtherNode(start).equals(end)) return existingRelationship;
}
return start.createRelationshipTo(end, type);
}
@Override
protected Iterable<Node> getStatesFromEntity(final Object entity) {
final Node entityNode = getState(entity);
final Set<Node> result = new HashSet<Node>();
for (final Relationship rel : entityNode.getRelationships(type, direction)) {
result.add(rel.getOtherNode(entityNode));
}
return result;
}
@Override
protected Node getState(final Object entity) {
return template.getPersistentState(entity);
}
}

View File

@@ -1,146 +0,0 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.mapping.*;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
public class OneToNRelationshipEntityFieldAccessorFactory implements FieldAccessorFactory {
private Neo4jTemplate template;
public OneToNRelationshipEntityFieldAccessorFactory(
Neo4jTemplate template) {
super();
this.template = template;
}
@Override
public boolean accept(final Neo4jPersistentProperty property) {
return property.isRelationship() && !property.getRelationshipInfo().targetsNodes() && property.getRelationshipInfo().isMultiple();
}
@Override
public FieldAccessor forField(final Neo4jPersistentProperty property) {
final RelationshipInfo relationshipInfo = property.getRelationshipInfo();
return new OneToNRelationshipEntityFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), (Class<?>) relationshipInfo.getTargetType().getType(), template,property);
}
public static class OneToNRelationshipEntityFieldAccessor extends AbstractNodeRelationshipFieldAccessor<Node, Relationship> {
private final boolean isEditableSet;
public OneToNRelationshipEntityFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> elementClass, final Neo4jTemplate template, Neo4jPersistentProperty property) {
super(elementClass, template, direction, type, property);
isEditableSet = Set.class.isAssignableFrom(this.property.getType());
}
@Override
public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) {
if (!isEditableSet) throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
final Node startNode = checkUnderlyingState(entity);
if (newVal == null) {
return null;
}
final Map<Node, Object> targetNodes = createSetOfTargetNodes(newVal, startNode);
removeMissingRelationships(startNode, targetNodes.keySet());
//createAddedRelationships(startNode, targetNodes.keySet());
persistEntities(targetNodes);
return createManagedSet(entity, (Set<?>) newVal, updateMappingPolicy(mappingPolicy));
}
private void persistEntities(Map<Node, Object> targetNodes) {
for (Object entry : targetNodes.values()) {
template.save(entry);
}
}
protected Map<Node, Object> createSetOfTargetNodes(Object newVal, Node startNode) {
if (!(newVal instanceof Set)) {
throw new IllegalArgumentException("New value must be a Set, was: " + newVal.getClass());
}
Map<Node,Object> targetNodes=new HashMap<Node,Object>();
for (Object entry : (Set<Object>) newVal) {
if (!relatedType.isInstance(entry)) {
throw new IllegalArgumentException("New value elements must be "+relatedType);
}
Neo4jPersistentEntity relationshipPEntity = property.getRelationshipInfo().getTargetEntity();
final RelationshipProperties relationshipProperties = relationshipPEntity.getRelationshipProperties();
final Neo4jPersistentProperty endNodeProperty = relationshipProperties.getEndNodeProperty();
final Object endNodeEntity = endNodeProperty.getValue(entry, endNodeProperty.getMappingPolicy());
final Node endNode = getState(endNodeEntity);
if (!endNode.equals(startNode)) {
targetNodes.put(endNode, entry);
} else {
final Neo4jPersistentProperty startNodeProperty = relationshipProperties.getStartNodeProperty();
final Node otherNode = getState(startNodeProperty.getValue(entry, startNodeProperty.getMappingPolicy()));
targetNodes.put(otherNode, entry);
}
}
return targetNodes;
}
@Override
public boolean isWriteable(Object entity) {
return isEditableSet;
}
@Override
public Object getValue(final Object entity, MappingPolicy mappingPolicy) {
checkUnderlyingState(entity);
final GraphBackedEntityIterableWrapper<Relationship, ?> result = iterableFrom(entity);
if (isEditableSet) {
@SuppressWarnings("unchecked") final ManagedFieldAccessorSet managedSet = createManagedSet(entity, IteratorUtil.addToCollection(result, new HashSet()), updateMappingPolicy(mappingPolicy));
return doReturn(managedSet);
}
return doReturn(result);
}
private GraphBackedEntityIterableWrapper<Relationship, ?> iterableFrom(final Object entity) {
return GraphBackedEntityIterableWrapper.create(getStatesFromEntity(entity), relatedType, template);
}
@Override
protected Iterable<Relationship> getStatesFromEntity(final Object entity) {
final Node node = getState(entity);
return node.getRelationships(type, direction);
}
@Override
protected Relationship obtainSingleRelationship(final Node start, final Node end) {
return null;
}
@Override
protected Node getState(final Object entity) {
return template.getPersistentState(entity);
}
}
}

View File

@@ -25,28 +25,30 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.mapping.RelationshipInfo;
import org.springframework.data.neo4j.support.Neo4jTemplate;
public class ReadOnlyOneToNRelationshipFieldAccessorFactory extends NodeRelationshipFieldAccessorFactory {
public class ReadOnlyRelatedToCollectionFieldAccessorFactory implements FieldAccessorFactory {
public ReadOnlyOneToNRelationshipFieldAccessorFactory(Neo4jTemplate template) {
super(template);
}
protected Neo4jTemplate template;
public ReadOnlyRelatedToCollectionFieldAccessorFactory(Neo4jTemplate template) {
this.template = template;
}
@Override
public boolean accept(final Neo4jPersistentProperty f) {
if (!f.isRelationship()) return false;
final RelationshipInfo info = f.getRelationshipInfo();
return info.isMultiple() && info.targetsNodes() && info.isReadonly();
public boolean accept(final Neo4jPersistentProperty property) {
if (!property.isRelationship()) return false;
final RelationshipInfo info = property.getRelationshipInfo();
return info.isCollection() && info.isRelatedTo() && info.isReadonly();
}
@Override
public FieldAccessor forField(final Neo4jPersistentProperty property) {
final RelationshipInfo relationshipInfo = property.getRelationshipInfo();
return new ReadOnlyOneToNRelationshipFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), (Class<?>) property.getRelationshipInfo().getTargetType().getType(), template,property);
return new ReadOnlyRelatedToCollectionFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), (Class<?>) property.getRelationshipInfo().getTargetType().getType(), template,property);
}
public static class ReadOnlyOneToNRelationshipFieldAccessor extends OneToNRelationshipFieldAccessorFactory.OneToNRelationshipFieldAccessor {
public static class ReadOnlyRelatedToCollectionFieldAccessor extends RelatedToCollectionFieldAccessorFactory.RelatedToCollectionFieldAccessor {
public ReadOnlyOneToNRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> elementClass, final Neo4jTemplate template, Neo4jPersistentProperty field) {
public ReadOnlyRelatedToCollectionFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> elementClass, final Neo4jTemplate template, Neo4jPersistentProperty field) {
super(type,direction,elementClass, template, field);
}

View File

@@ -24,61 +24,63 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.mapping.RelationshipInfo;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
public class OneToNRelationshipFieldAccessorFactory extends NodeRelationshipFieldAccessorFactory {
public OneToNRelationshipFieldAccessorFactory(Neo4jTemplate template) {
super(template);
}
public class RelatedToCollectionFieldAccessorFactory implements FieldAccessorFactory {
protected Neo4jTemplate template;
public RelatedToCollectionFieldAccessorFactory(Neo4jTemplate template) {
this.template = template;
}
@Override
public boolean accept(final Neo4jPersistentProperty property) {
if (!property.isRelationship()) return false;
final RelationshipInfo info = property.getRelationshipInfo();
return info.isMultiple() && info.targetsNodes() && !info.isReadonly();
return info.isCollection() && info.isRelatedTo() && !info.isReadonly();
}
@Override
public FieldAccessor forField(final Neo4jPersistentProperty property) {
final RelationshipInfo relationshipInfo = property.getRelationshipInfo();
final Class<?> targetType = relationshipInfo.getTargetType().getType();
return new OneToNRelationshipFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), targetType, template,property);
return new RelatedToCollectionFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), targetType, template,property);
}
public static class OneToNRelationshipFieldAccessor extends NodeToNodesRelationshipFieldAccessor {
public static class RelatedToCollectionFieldAccessor extends RelatedToFieldAccessor {
public OneToNRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> elementClass, final Neo4jTemplate template, Neo4jPersistentProperty property) {
public RelatedToCollectionFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> elementClass, final Neo4jTemplate template, Neo4jPersistentProperty property) {
super(elementClass, template, direction, type,property);
}
public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) {
final Node node = checkUnderlyingState(entity);
if (newVal == null) {
/* null should not remove existing relationships but leave them alone
removeMissingRelationships(node, Collections.<Node>emptySet());
*/
return null;
}
final Node node = checkAndGetNode(entity);
// null should not remove existing relationships but leave them alone
if (newVal == null) return null;
final Set<Node> targetNodes = createSetOfTargetNodes(newVal);
removeMissingRelationships(node, targetNodes);
createAddedRelationships(node, targetNodes);
return createManagedSet(entity, (Set<?>) newVal, updateMappingPolicy(mappingPolicy));
return createManagedSet(entity, (Set<?>) newVal, property.obtainMappingPolicy(mappingPolicy));
}
@Override
public Object getValue(final Object entity, MappingPolicy mappingPolicy) {
checkUnderlyingState(entity);
final MappingPolicy currentPolicy = updateMappingPolicy(mappingPolicy);
checkAndGetNode(entity);
final MappingPolicy currentPolicy = property.obtainMappingPolicy(mappingPolicy);
final Set<?> result = createEntitySetFromRelationshipEndNodes(entity, currentPolicy);
return doReturn(createManagedSet(entity, result, currentPolicy));
}
@Override
public Object getDefaultValue() {
// todo delegate to property
if (List.class.isAssignableFrom(property.getType())) return new ArrayList();
return new HashSet();
}
}

View File

@@ -0,0 +1,83 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import java.util.Set;
/**
* @author Michael Hunger
* @since 11.09.2010
*/
public abstract class RelatedToFieldAccessor implements FieldAccessor {
protected final RelationshipType type;
protected final Neo4jPersistentProperty property;
protected final Direction direction;
protected final Class<?> relatedType;
protected final Neo4jTemplate template;
protected RelationshipHelper relationshipHelper;
public RelatedToFieldAccessor(Class<?> relatedType, Neo4jTemplate template, Direction direction, RelationshipType type, Neo4jPersistentProperty property) {
this.relationshipHelper = new RelationshipHelper(template, direction, type);
this.relatedType = relatedType;
this.template = template;
this.direction = direction;
this.type = type;
this.property = property;
}
@Override
public boolean isWriteable(Object entity) {
return true;
}
protected <T> ManagedFieldAccessorSet<T> createManagedSet(Object entity, Set<T> result, MappingPolicy mappingPolicy) {
return ManagedFieldAccessorSet.create(entity, result, mappingPolicy, property, template, this);
}
public Object getDefaultValue() {
return null;
}
// delegating methods
protected Node checkAndGetNode(Object entity) {
return relationshipHelper.checkAndGetNode(entity);
}
protected void removeMissingRelationships(Node node, Set<Node> targetNodes) {
relationshipHelper.removeMissingRelationshipsInStoreAndKeepOnlyNewRelationShipsInSet(node, targetNodes);
}
protected void createAddedRelationships(Node node, Set<Node> targetNodes) {
relationshipHelper.createAddedRelationships(node, targetNodes);
}
protected Set<Node> createSetOfTargetNodes(Object newVal) {
return relationshipHelper.createSetOfTargetNodes(newVal, relatedType);
}
protected Set<Object> createEntitySetFromRelationshipEndNodes(Object entity, MappingPolicy mappingPolicy) {
return relationshipHelper.createEntitySetFromRelationshipEndNodes(entity, mappingPolicy, relatedType);
}
}

View File

@@ -30,31 +30,34 @@ import java.util.Set;
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
public class SingleRelationshipFieldAccessorFactory extends NodeRelationshipFieldAccessorFactory {
public class RelatedToSingleFieldAccessorFactory implements FieldAccessorFactory {
public SingleRelationshipFieldAccessorFactory(Neo4jTemplate template) {
super(template);
protected Neo4jTemplate template;
public RelatedToSingleFieldAccessorFactory(Neo4jTemplate template) {
this.template = template;
}
@Override
public boolean accept(final Neo4jPersistentProperty property) {
return property.isRelationship() && property.getRelationshipInfo().targetsNodes() && !property.getRelationshipInfo().isMultiple();
if (!property.isRelationship()) return false;
return property.getRelationshipInfo().isRelatedTo() && property.getRelationshipInfo().isSingle();
}
@Override
public FieldAccessor forField(final Neo4jPersistentProperty property) {
final RelationshipInfo relationshipInfo = property.getRelationshipInfo();
return new SingleRelationshipFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), (Class<?>) relationshipInfo.getTargetType().getType(), template,property);
return new RelatedToSingleFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), (Class<?>) relationshipInfo.getTargetType().getType(), template,property);
}
public static class SingleRelationshipFieldAccessor extends NodeToNodesRelationshipFieldAccessor {
public SingleRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> clazz, final Neo4jTemplate template, Neo4jPersistentProperty property) {
public static class RelatedToSingleFieldAccessor extends RelatedToFieldAccessor {
public RelatedToSingleFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> clazz, final Neo4jTemplate template, Neo4jPersistentProperty property) {
super(clazz, template, direction, type, property);
}
@Override
public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) {
final Node node= checkUnderlyingState(entity);
final Node node= checkAndGetNode(entity);
if (newVal == null) {
removeMissingRelationships(node, Collections.<Node>emptySet());
return null;
@@ -67,8 +70,8 @@ public class SingleRelationshipFieldAccessorFactory extends NodeRelationshipFiel
@Override
public Object getValue(final Object entity, MappingPolicy mappingPolicy) {
checkUnderlyingState(entity);
final Set<Object> result = createEntitySetFromRelationshipEndNodes(entity, updateMappingPolicy(mappingPolicy));
checkAndGetNode(entity);
final Set<Object> result = createEntitySetFromRelationshipEndNodes(entity, property.obtainMappingPolicy(mappingPolicy));
final Object singleEntity = result.isEmpty() ? null : result.iterator().next();
return doReturn(singleEntity);
}

View File

@@ -0,0 +1,135 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.mapping.RelationshipInfo;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
public class RelatedToViaCollectionFieldAccessorFactory implements FieldAccessorFactory {
private Neo4jTemplate template;
public RelatedToViaCollectionFieldAccessorFactory(Neo4jTemplate template) {
this.template = template;
}
@Override
public boolean accept(final Neo4jPersistentProperty property) {
if (!property.isRelationship()) return false;
return property.getRelationshipInfo().isRelatedToVia() && property.getRelationshipInfo().isCollection();
}
@Override
public FieldAccessor forField(final Neo4jPersistentProperty property) {
final RelationshipInfo relationshipInfo = property.getRelationshipInfo();
return new RelatedToViaCollectionFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), (Class<?>) relationshipInfo.getTargetType().getType(), template,property);
}
public static class RelatedToViaCollectionFieldAccessor implements FieldAccessor {
private final boolean isMutableCollection;
private final Class<?> relatedType;
private final Neo4jTemplate template;
private final Neo4jPersistentProperty property;
private final RelationshipHelper relationshipHelper;
private final RelationshipEntities relationshipEntities;
public RelatedToViaCollectionFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> relatedType, final Neo4jTemplate template, Neo4jPersistentProperty property) {
relationshipHelper = new RelationshipHelper(template, direction, type);
this.relatedType = relatedType;
this.template = template;
this.property = property;
isMutableCollection = Collection.class.isAssignableFrom(property.getType());
relationshipEntities = new RelationshipEntities(relationshipHelper, property);
}
@Override
public Object getDefaultValue() {
// todo delegate to property
if (List.class.isAssignableFrom(property.getType())) return new ArrayList();
return new HashSet();
}
@Override
public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) {
if (!isMutableCollection) throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
final Node startNode = relationshipHelper.checkAndGetNode(entity);
// null collections values are ignored, not deleting relationships
if (newVal == null) return null;
final Map<Node, Object> endNodeToEntityMapping = loadEndNodeToRelationshipEntityMapping(newVal, startNode);
relationshipHelper.removeMissingRelationshipsInStoreAndKeepOnlyNewRelationShipsInSet(startNode, endNodeToEntityMapping.keySet());
persistEntities(endNodeToEntityMapping.values());
return createManagedSet(entity, (Set<?>) newVal, property.obtainMappingPolicy(mappingPolicy));
}
private void persistEntities(final Collection<Object> relationshipEntities) {
for (Object entity : relationshipEntities) {
template.save(entity);
}
}
protected Map<Node, Object> loadEndNodeToRelationshipEntityMapping(Object newVal, Node startNode) {
if (!(newVal instanceof Set)) {
throw new IllegalArgumentException("New value must be at least an Iterable, was: " + newVal.getClass());
}
return relationshipEntities.loadEndNodeToRelationshipEntityMapping(startNode, (Iterable<Object>) newVal, relatedType);
}
@Override
public boolean isWriteable(Object entity) {
return isMutableCollection;
}
@Override
public Object getValue(final Object entity, MappingPolicy mappingPolicy) {
final Node node = relationshipHelper.checkAndGetNode(entity);
final GraphBackedEntityIterableWrapper<Relationship, ?> result = loadRelationshipEntities(node);
if (isMutableCollection) {
@SuppressWarnings("unchecked") final ManagedFieldAccessorSet managedSet = createManagedSet(entity, IteratorUtil.addToCollection(result, new HashSet()), property.obtainMappingPolicy(mappingPolicy));
return doReturn(managedSet);
}
return doReturn(result);
}
protected <T> ManagedFieldAccessorSet<T> createManagedSet(Object entity, Set<T> result, MappingPolicy mappingPolicy) {
return ManagedFieldAccessorSet.create(entity, result, mappingPolicy, property, template, this);
}
private GraphBackedEntityIterableWrapper<Relationship, ?> loadRelationshipEntities(final Node node) {
return GraphBackedEntityIterableWrapper.create(relationshipHelper.getRelationships(node), relatedType, template);
}
}
}

View File

@@ -0,0 +1,108 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.mapping.RelationshipInfo;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
public class RelatedToViaSingleFieldAccessorFactory implements FieldAccessorFactory {
private Neo4jTemplate template;
public RelatedToViaSingleFieldAccessorFactory(Neo4jTemplate template) {
this.template = template;
}
@Override
public boolean accept(final Neo4jPersistentProperty property) {
if (!property.isRelationship()) return false;
return property.getRelationshipInfo().isRelatedToVia() && property.getRelationshipInfo().isSingle();
}
@Override
public FieldAccessor forField(final Neo4jPersistentProperty property) {
final RelationshipInfo relationshipInfo = property.getRelationshipInfo();
return new RelatedToViaSingleFieldAccessor(relationshipInfo.getRelationshipType(), relationshipInfo.getDirection(), (Class<?>) relationshipInfo.getTargetType().getType(), template,property);
}
public static class RelatedToViaSingleFieldAccessor implements FieldAccessor {
private final Class<?> relatedType;
private final Neo4jTemplate template;
private final Neo4jPersistentProperty property;
private final RelationshipHelper relationshipHelper;
private final RelationshipEntities relationshipEntities;
public RelatedToViaSingleFieldAccessor(final RelationshipType type, final Direction direction, final Class<?> relatedType, final Neo4jTemplate template, Neo4jPersistentProperty property) {
relationshipHelper = new RelationshipHelper(template, direction, type);
relationshipEntities = new RelationshipEntities(relationshipHelper, property);
this.relatedType = relatedType;
this.template = template;
this.property = property;
}
@Override
public Object getDefaultValue() {
return null;
}
@Override
public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) {
final Node startNode = relationshipHelper.checkAndGetNode(entity);
final Map<Node,Object> endNodeToEntityMapping = relationshipEntities.loadEndNodeToRelationshipEntityMapping(startNode, toSet(newVal), relatedType);
relationshipHelper.removeMissingRelationshipsInStoreAndKeepOnlyNewRelationShipsInSet(startNode, endNodeToEntityMapping.keySet());
persistEntities(endNodeToEntityMapping.values());
return newVal;
}
private Iterable<Object> toSet(Object newVal) {
if (newVal==null) return Collections.emptySet();
return Collections.singleton(newVal);
}
private void persistEntities(final Collection<Object> relationshipEntities) {
for (Object entity : relationshipEntities) {
template.save(entity);
}
}
@Override
public boolean isWriteable(Object entity) {
return true;
}
@Override
public Object getValue(final Object entity, MappingPolicy mappingPolicy) {
final Node node = relationshipHelper.checkAndGetNode(entity);
Relationship rel = relationshipHelper.getSingleRelationship(node);
return doReturn(rel==null ? null : template.load(rel,relatedType));
}
}
}

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.fieldaccess;
import org.neo4j.graphdb.Node;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.mapping.RelationshipProperties;
import java.util.HashMap;
import java.util.Map;
/**
* @author mh
* @since 28.02.12
*/
class RelationshipEntities {
private final RelationshipHelper relationshipHelper;
private final Neo4jPersistentProperty property;
private final RelationshipProperties relationshipProperties;
private final Neo4jPersistentProperty endNodeProperty;
private final Neo4jPersistentProperty startNodeProperty;
private final MappingPolicy endNodeMappingPolicy;
private final MappingPolicy startNodeMappingPolicy;
public RelationshipEntities(RelationshipHelper relationshipHelper, Neo4jPersistentProperty property) {
this.relationshipHelper = relationshipHelper;
this.property = property;
relationshipProperties = property.getRelationshipInfo().getTargetEntity().getRelationshipProperties();
endNodeProperty = relationshipProperties.getEndNodeProperty();
startNodeProperty = relationshipProperties.getStartNodeProperty();
endNodeMappingPolicy = endNodeProperty.getMappingPolicy();
startNodeMappingPolicy = startNodeProperty.getMappingPolicy();
}
public Node getOtherNode(Node startNode, Object relationshipEntity) {
final Node endNode = relationshipHelper.getNode(endNodeProperty.getValue(relationshipEntity, endNodeMappingPolicy));
if (startNode.equals(endNode)) {
return relationshipHelper.getNode(startNodeProperty.getValue(relationshipEntity, startNodeMappingPolicy));
} else {
return endNode;
}
}
public Map<Node, Object> loadEndNodeToRelationshipEntityMapping(Node startNode, Iterable<Object> values, Class<?> relatedType) {
Map<Node, Object> endNodeToEntityMapping = new HashMap<Node, Object>();
for (Object entry : values) {
if (!relatedType.isInstance(entry))
throw new IllegalArgumentException("Elements of " + property + " collection must be of " + relatedType);
endNodeToEntityMapping.put(getOtherNode(startNode, entry), entry);
}
return endNodeToEntityMapping;
}
}

View File

@@ -0,0 +1,151 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.util.Assert;
import java.util.HashSet;
import java.util.Set;
/**
* @author mh
* @since 28.02.12
*/
public class RelationshipHelper {
private final Neo4jTemplate template;
private final Direction direction;
private final RelationshipType type;
public RelationshipHelper(Neo4jTemplate template, Direction direction, RelationshipType type) {
this.template = template;
this.direction = direction;
this.type = type;
}
private Iterable<Node> getOtherNodes(Node node) {
final Set<Node> result = new HashSet<Node>();
for (final Relationship rel : node.getRelationships(type, direction)) {
result.add(rel.getOtherNode(node));
}
return result;
}
protected Relationship obtainSingleRelationship(final Node start, final Node end) {
final Iterable<Relationship> existingRelationships = start.getRelationships(type, direction);
for (final Relationship existingRelationship : existingRelationships) {
if (existingRelationship != null && existingRelationship.getOtherNode(start).equals(end))
return existingRelationship;
}
return start.createRelationshipTo(end, type);
}
protected Node checkAndGetNode(Object entity) {
if (entity == null) throw new IllegalStateException("Entity is null");
Node node = getNode(entity);
if (node != null) return node;
throw new IllegalStateException("Entity must have a backing Node");
}
protected void removeMissingRelationshipsInStoreAndKeepOnlyNewRelationShipsInSet(Node node, Set<Node> targetNodes) {
for (Relationship relationship : node.getRelationships(type, direction)) {
if (!targetNodes.remove(relationship.getOtherNode(node)))
relationship.delete();
}
}
protected void createAddedRelationships(Node node, Set<Node> targetNodes) {
for (Node targetNode : targetNodes) {
createSingleRelationship(node, targetNode);
}
}
// adding cascade
@SuppressWarnings("unchecked")
protected Set<Node> createSetOfTargetNodes(Object newVal, final Class<?> relatedType) {
if (!(newVal instanceof Set)) {
throw new IllegalArgumentException("New value must be a Set, was: " + newVal.getClass());
}
Set<Node> nodes = new HashSet<Node>();
for (Object value : (Set<Object>) newVal) {
if (!relatedType.isInstance(value)) {
throw new IllegalArgumentException("New value elements must be " + relatedType);
}
nodes.add(getOrCreateState(value));
}
return nodes;
}
protected Node getOrCreateState(Object value) {
final Node Node = getNode(value);
if (Node != null) return Node;
final Object saved = template.save(value);
final Node newState = getNode(saved);
Assert.notNull(newState);
return newState;
}
protected Set<Object> createEntitySetFromRelationshipEndNodes(Object entity, final MappingPolicy mappingPolicy, final Class<?> relatedType) {
final Iterable<Node> nodes = getStatesFromEntity(entity);
final Set<Object> result = new HashSet<Object>();
for (final Node otherNode : nodes) {
Object target = template.createEntityFromState(otherNode, relatedType, mappingPolicy);
result.add(target);
}
return result;
}
@SuppressWarnings("unchecked")
protected Relationship createSingleRelationship(Node start, Node end) {
if (end == null) return null;
switch (direction) {
case OUTGOING:
case BOTH: { // TODO both should actually check in both directions, perhaps have the obtain method get the direction instead and figure out what to do itself
return obtainSingleRelationship(start, end);
}
case INCOMING:
return obtainSingleRelationship(end, start);
default:
throw new InvalidDataAccessApiUsageException("invalid direction " + direction);
}
}
protected Iterable<Node> getStatesFromEntity(final Object entity) {
final Node node = getNode(entity);
return getOtherNodes(node);
}
protected Node getNode(final Object entity) {
return template.getPersistentState(entity);
}
public Iterable<Relationship> getRelationships(Node node) {
return node.getRelationships(type, direction);
}
public Relationship getSingleRelationship(Node node) {
return node.getSingleRelationship(type,direction);
}
}

View File

@@ -92,4 +92,6 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
Class<?> getPropertyType();
boolean isUnique();
MappingPolicy obtainMappingPolicy(MappingPolicy currentMappingPolicy);
}

View File

@@ -30,11 +30,11 @@ import java.lang.reflect.Field;
public class RelationshipInfo {
private boolean isMultiple;
private boolean isCollection;
private final Direction direction;
private final String type;
private final TypeInformation<?> targetType;
private final boolean targetsNodes;
private final boolean relatedTo;
private boolean readonly;
private Neo4jPersistentEntity targetEntity;
@@ -49,18 +49,21 @@ public class RelationshipInfo {
return DynamicRelationshipType.withName(type);
}
public boolean isMultiple() {
return isMultiple;
public boolean isCollection() {
return isCollection;
}
public boolean isSingle() {
return !isCollection;
}
public RelationshipInfo(String type, Direction direction, TypeInformation<?> typeInformation, TypeInformation<?> concreteActualType, Neo4jMappingContext ctx) {
this.type = type;
this.direction = direction;
isMultiple = typeInformation.isCollectionLike();
isCollection = typeInformation.isCollectionLike();
targetType = concreteActualType!=null ? concreteActualType : typeInformation.getActualType();
this.targetEntity = ctx.getPersistentEntity(targetType);
targetsNodes = targetEntity.isNodeEntity();
this.readonly = isMultiple() && typeInformation.getType().equals(Iterable.class);
relatedTo = targetEntity.isNodeEntity();
this.readonly = isCollection() && typeInformation.getType().equals(Iterable.class);
}
public static RelationshipInfo fromField(Field field, TypeInformation<?> typeInformation, Neo4jMappingContext ctx) {
@@ -104,8 +107,11 @@ public class RelationshipInfo {
return targetType;
}
public boolean targetsNodes() {
return targetsNodes;
public boolean isRelatedTo() {
return relatedTo;
}
public boolean isRelatedToVia() {
return !isRelatedTo();
}
public boolean isReadonly() {

View File

@@ -34,6 +34,7 @@ import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
import org.springframework.data.neo4j.core.UncategorizedGraphStoreException;
import org.springframework.data.neo4j.fieldaccess.GraphBackedEntityIterableWrapper;
import org.springframework.data.neo4j.mapping.EntityPersister;
import org.springframework.data.neo4j.mapping.IndexInfo;
import org.springframework.data.neo4j.mapping.MappingPolicy;
@@ -253,8 +254,8 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
* properties are used to initialize the node.
*/
@Override
public Node getOrCreateNode(String index, String key, Object value, final Map<String,Object> properties) {
return infrastructure.getGraphDatabase().getOrCreateNode(index,key,value,properties);
public Node getOrCreateNode(String index, String key, Object value, final Map<String, Object> properties) {
return infrastructure.getGraphDatabase().getOrCreateNode(index, key, value, properties);
}
@Override
@@ -341,6 +342,15 @@ public class Neo4jTemplate implements Neo4jOperations, EntityPersister {
return infrastructure.getEntityPersister().createEntityFromState(relationship, relationshipEntityClass, persistentEntity.getMappingPolicy());
}
@Override
public <R> Iterable<R> getRelationshipsBetween(Object start, Object end, Class<R> relationshipEntityClass, String relationshipType) {
notNull(start,"start",end,"end",relationshipEntityClass,"relationshipEntityClass",relationshipType,"relationshipType");
final Iterable<Relationship> relationships = infrastructure.getEntityStateHandler().getRelationshipsBetween(start, end, relationshipType);
if (relationships == null) return null;
if (Relationship.class.isAssignableFrom(relationshipEntityClass)) return (Iterable<R>)relationships;
return GraphBackedEntityIterableWrapper.create(relationships, relationshipEntityClass, this);
}
@Override
public Relationship getRelationshipBetween(Object start, Object end, String relationshipType) {
notNull(start,"start",end,"end",relationshipType,"relationshipType");

View File

@@ -31,7 +31,9 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.mapping.RelationshipProperties;
import org.springframework.data.neo4j.mapping.RelationshipResult;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author mh
@@ -245,4 +247,17 @@ public class EntityStateHandler {
}
public Iterable<Relationship> getRelationshipsBetween(Object source, Object target, String type) {
if (source == null) throw new IllegalArgumentException("Source entity is null");
if (target == null) throw new IllegalArgumentException("Target entity is null");
if (type == null) throw new IllegalArgumentException("Relationshiptype is null");
Node node = getPersistentState(source);
Node targetNode = getPersistentState(target);
if (node == null || targetNode == null) return null;
List<Relationship> result=new ArrayList<Relationship>();
for (Relationship relationship : node.getRelationships(DynamicRelationshipType.withName(type),Direction.OUTGOING)) {
if (relationship.getEndNode().equals(targetNode)) result.add(relationship);
}
return result;
}
}

View File

@@ -348,4 +348,9 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
}
return result;
}
public MappingPolicy obtainMappingPolicy(MappingPolicy providedMappingPolicy) {
if (providedMappingPolicy != null) return providedMappingPolicy;
return getMappingPolicy();
}
}

View File

@@ -111,6 +111,12 @@ public interface Neo4jOperations {
*/
<R> R getRelationshipBetween(Object start, Object end, Class<R> relationshipEntityClass, String relationshipType);
/**
* Retrieves all relationship entities between two node entities with the given relationship type projected to the provided
* relationship entity class
*/
<R> Iterable<R> getRelationshipsBetween(Object start, Object end, Class<R> relationshipEntityClass, String relationshipType);
/**
* Retrieves a single relationship entity between two node entities.
*/

View File

@@ -50,6 +50,7 @@ import org.springframework.transaction.support.TransactionTemplate;
import java.util.*;
import static org.junit.Assert.*;
import static org.junit.internal.matchers.IsCollectionContaining.hasItems;
import static org.neo4j.graphdb.Direction.OUTGOING;
import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
import static org.neo4j.helpers.collection.IteratorUtil.first;
@@ -295,6 +296,13 @@ public class EntityNeo4jTemplateTest extends EntityTestBase {
final Friendship knows = neo4jOperations.getRelationshipBetween(testTeam.michael, testTeam.david, Friendship.class, "knows");
assertEquals(testTeam.friendShip.getId(),knows.getId());
}
@Test @Transactional
public void testGetMultipleRelationshipBetween() throws Exception {
final Friendship friendship = neo4jOperations.getRelationshipBetween(testTeam.michael, testTeam.david, Friendship.class, "knows");
final Friendship friendship2 = neo4jOperations.createRelationshipBetween(testTeam.michael, testTeam.david, Friendship.class, "knows", true);
final Iterable<Friendship> allFriendships = neo4jOperations.getRelationshipsBetween(testTeam.michael, testTeam.david, Friendship.class, "knows");
assertThat(allFriendships, hasItems(friendship, friendship2));
}
@Test @Transactional
public void testDeleteRelationshipBetween() throws Exception {

View File

@@ -122,19 +122,19 @@ public class Actor {
To access the full data model of graph relationships, POJOs can also be annotated with
<code>@RelationshipEntity</code>, making them relationship entities. Just as node entities represent
nodes in the graph, relationship entities represent relationships. As described above,
fields annotated with <code>@RelatedTo</code> provide a way to link node entities together
fields annotated with <code>@RelatedTo</code> provide a way to only link node entities
via relationships, but it provides no way of accessing the relationships themselves.
</para>
<para>
Relationship entities can be accessed via by @RelatedToVia-annotated (<xref linkend="reference:programming_model:relationships:relatedtovia"/>)
fields or methods like <code>entity.getRelationshipTo()</code>
or <code>template|repository.getRelationshipsBetween()</code>.
or <code>template|repository.getRelationship(s)Between()</code>.
</para>
<para>
Relationship entities either be instantiated directly and added to
<code>Set's</code> of <code>@RelatedToVia</code> fields or created by the introduced
Relationship entities either be instantiated directly and set or added to
<code>@RelatedToVia</code>-annotated fields or created by the introduced
<code>entity.relateTo(), template|repository.createRelationshipBetween()</code> methods
(see <xref linkend="reference:programming-model:introduced-methods"/>)
(see alos <xref linkend="reference:programming-model:introduced-methods"/>)
</para>
<para>
Fields in relationship entities are, similarly to node entities, persisted as properties on
@@ -174,19 +174,22 @@ public class Role {
<para>
To provide easy programmatic access to the richer relationship entities of the data model,
the annotation <code>@RelatedToVia</code> can be added on fields of type
<code>Iterable&lt;T></code> or <code>Set&lt;T></code>, where T is a <code>@RelationshipEntity</code>-annotated
<code>Iterable&lt;T></code> or <code>Set&lt;T></code> or T, where T is a <code>@RelationshipEntity</code>-annotated
class. These fields provide access to relationship entities.
</para>
<example>
<title>Relationship entity (in simple mapping)</title>
<programlisting language="java"><![CDATA[@NodeEntity
public class Actor {
@RelatedToVia
@Set<Role> roles=new HashSet<Role>();
public Role playedIn(Movie movie, String title) {
Role role=new Role(this,movie,title);
roles.add(role);
return role;
}
@RelatedToVia(type="FRIEND_OF", direction=Direction.INCOMING)
Friendship bestFriend;
}
@RelationshipEntity(type = "ACTS_IN")
@@ -195,6 +198,13 @@ public class Role {
@StartNode private Actor actor;
@EndNode private Movie movie;
}
@RelationshipEntity
public class Friendship {
Date since;
@StartNode private Actor actor;
@EndNode private Person buddy;
}
]]></programlisting>