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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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>() {
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -92,4 +92,6 @@ public interface Neo4jPersistentProperty extends PersistentProperty<Neo4jPersist
|
||||
Class<?> getPropertyType();
|
||||
|
||||
boolean isUnique();
|
||||
|
||||
MappingPolicy obtainMappingPolicy(MappingPolicy currentMappingPolicy);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,4 +348,9 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public MappingPolicy obtainMappingPolicy(MappingPolicy providedMappingPolicy) {
|
||||
if (providedMappingPolicy != null) return providedMappingPolicy;
|
||||
return getMappingPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<T></code> or <code>Set<T></code>, where T is a <code>@RelationshipEntity</code>-annotated
|
||||
<code>Iterable<T></code> or <code>Set<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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user