diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/NodeEntityTests.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/NodeEntityTests.java index 3b2365277..87a9665cb 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/NodeEntityTests.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/NodeEntityTests.java @@ -108,6 +108,8 @@ public class NodeEntityTests extends EntityTestBase { public void testLabels() { String[] labelNames = {"Person", "Developer", "Father","_Person"}; Person p = new Person("Michael",39).persist(); +// assertThat(p.getLabels(), hasItems(labelNames[0],labelNames[3])); + p = neo4jTemplate.findOne(p.getId(), Person.class); assertThat(p.getLabels(), hasItems(labelNames[0],labelNames[3])); p.addLabel(labelNames[1]); p.addLabel(labelNames[2]); diff --git a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java index 15759dec4..7c82b792e 100644 --- a/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java +++ b/spring-data-neo4j-rest/src/main/java/org/springframework/data/neo4j/rest/SpringRestGraphDatabase.java @@ -22,7 +22,6 @@ import org.neo4j.rest.graphdb.RestAPIFacade; import org.neo4j.rest.graphdb.entity.RestNode; import org.neo4j.rest.graphdb.index.RestIndex; import org.neo4j.rest.graphdb.index.RestIndexManager; -import org.neo4j.rest.graphdb.query.RestCypherQueryEngine; import org.neo4j.rest.graphdb.transaction.NullTransaction; import org.neo4j.rest.graphdb.transaction.NullTransactionManager; import org.neo4j.rest.graphdb.util.Config; @@ -91,10 +90,12 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat } @Override - public Node getOrCreateNode(String indexName, String key, Object value, final Map properties) { + public Node getOrCreateNode(String indexName, String key, Object value, final Map properties, Collection labels) { if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null"); final RestIndex nodeIndex = index().forNodes(indexName); - return getRestAPI().getOrCreateNode(nodeIndex, key, value, properties); + RestNode node = getRestAPI().getOrCreateNode(nodeIndex, key, value, properties); + getRestAPI().addLabels(node,toLabels(labels)); + return node; } @Override diff --git a/spring-data-neo4j/pom.xml b/spring-data-neo4j/pom.xml index a4eaefd6a..ec0776ccb 100644 --- a/spring-data-neo4j/pom.xml +++ b/spring-data-neo4j/pom.xml @@ -217,6 +217,12 @@ provided + + ch.qos.logback + logback-core + ${logback} + test + diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java index 28d0e67ec..c68365921 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/core/GraphDatabase.java @@ -52,7 +52,7 @@ public interface GraphDatabase { * creates the node uniquely or returns an existing node with the same index-key-value combination. * properties are used to initialize the node. */ - Node getOrCreateNode(String indexName, String key, Object value, final Map properties); + Node getOrCreateNode(String indexName, String key, Object value, final Map properties, Collection labels); /** * @param id relationship id diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/LabelFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/LabelFieldAccessorFactory.java new file mode 100644 index 000000000..23c00b66f --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/LabelFieldAccessorFactory.java @@ -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.DynamicLabel; +import org.neo4j.graphdb.Label; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.PropertyContainer; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.neo4j.annotation.Labels; +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; +import java.util.TreeSet; + +import static org.springframework.data.neo4j.support.DoReturn.doReturn; + +public class LabelFieldAccessorFactory implements FieldAccessorFactory { + private final Neo4jTemplate template; + + public LabelFieldAccessorFactory(Neo4jTemplate template) { + this.template = template; + } + + @Override + public boolean accept(final Neo4jPersistentProperty property) { + return property.isAnnotationPresent(Labels.class); + } + + @Override + public FieldAccessor forField(final Neo4jPersistentProperty property) { + return new LabelFieldAccessor(property, template); + } + + public static class LabelFieldAccessor implements FieldAccessor { + protected final Neo4jPersistentProperty property; + private final Neo4jTemplate template; + + public LabelFieldAccessor(final Neo4jPersistentProperty property, Neo4jTemplate template) { + this.property = property; + this.template = template; + } + + @Override + public boolean isWriteable(Object entity) { + return true; + } + + @Override + public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) { + if (entity==null) return entity; + final PropertyContainer state = template.getPersistentState(entity); + if (state instanceof Node) { + Node node = (Node) state; + Set oldLabels = getLabels(node); + for (String newLabel : (Iterable) newVal) { + if (oldLabels.remove(newLabel)) continue; + node.addLabel(DynamicLabel.label(newLabel)); + } + for (String removedLabels : oldLabels) { + node.removeLabel(DynamicLabel.label(removedLabels)); + } + return doReturn(newVal); + } + throw new MappingException("Error setting labels on "+entity); + } + + @Override + public Object getValue(final Object entity, MappingPolicy mappingPolicy) { + final PropertyContainer state = template.getPersistentState(entity); + if (state instanceof Node) { + return doReturn(getLabels((Node) state)); + } + throw new MappingException("Error retrieving labels from "+entity); + } + + private Set getLabels(Node state) { + Set labels = new TreeSet<>(); + for (Label label : state.getLabels()) { + labels.add(label.name()); + } + return labels; + } + + @Override + public Object getDefaultValue() { + return null; + } + + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java index cb1c2e37c..8b1f6c814 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/DelegatingGraphDatabase.java @@ -287,17 +287,26 @@ public class DelegatingGraphDatabase implements GraphDatabase { return schemaIndexProvider.merge(labelName,key,value,nodeProperties,labels); } - public Node getOrCreateNode(String indexName, String key, Object value, final Map nodeProperties) { + public Node getOrCreateNode(String indexName, String key, Object value, final Map nodeProperties, final Collection labels) { if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null"); if (value instanceof Number) value= ValueContext.numeric((Number)value); UniqueFactory.UniqueNodeFactory factory = new UniqueFactory.UniqueNodeFactory(delegate, indexName) { protected void initialize(Node node, Map _) { setProperties(node,nodeProperties); + setLabels(node,labels); } }; return factory.getOrCreate(key, value); } + private Node setLabels(Node node, Collection labels) { + if (labels==null || labels.isEmpty()) return node; + for (String label : labels) { + node.addLabel(DynamicLabel.label(label)); + } + return node; + } + @Override public Relationship getOrCreateRelationship(String indexName, String key, Object value, final Node startNode, final Node endNode, final String type, final Map properties) { if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null"); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java index 33cf24736..f13815a8c 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Neo4jTemplate.java @@ -296,8 +296,8 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware { * properties are used to initialize the node. */ @Override - public Node getOrCreateNode(String index, String key, Object value, final Map properties) { - return getGraphDatabase().getOrCreateNode(index, key, value, properties); + public Node getOrCreateNode(String index, String key, Object value, final Map properties, Collection labels) { + return getGraphDatabase().getOrCreateNode(index, key, value, properties, labels); } /** @@ -739,7 +739,7 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware { return getGraphDatabase().merge(indexInfo.getIndexName(),indexInfo.getIndexKey(),value, Collections.emptyMap(), persistentEntity.getAllLabels()); } else { if (value instanceof Number && indexInfo.isNumeric()) value = ValueContext.numeric((Number) value); - return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap()); + return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap(), persistentEntity.getAllLabels()); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityStateHandler.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityStateHandler.java index 5a82d6e54..634c24cd7 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityStateHandler.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityStateHandler.java @@ -153,7 +153,7 @@ public class EntityStateHandler { if (indexInfo.isLabelBased()) { return graphDatabase.merge(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap(), persistentEntity.getAllLabels()); } else { - return graphDatabase.getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap()); + return graphDatabase.getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap(),persistentEntity.getAllLabels()); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java index 200d21326..5fd0f88b1 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java @@ -109,7 +109,7 @@ public class Neo4jMappingContext extends AbstractMappingContext type) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java index f6de9ba00..69e34902d 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jPersistentEntityImpl.java @@ -31,6 +31,7 @@ import org.springframework.data.mapping.model.MappingException; import org.springframework.data.neo4j.annotation.NodeEntity; import org.springframework.data.neo4j.annotation.RelationshipEntity; import org.springframework.data.neo4j.mapping.*; +import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy; import org.springframework.data.util.TypeInformation; /** @@ -252,6 +253,8 @@ public class Neo4jPersistentEntityImpl extends BasicPersistentEntity labels = collectSuperTypeLabels(storedType, new LinkedHashSet()); labels.addAll(computeIndexBasedLabels(this)); labels.add(alias); + // TODO workaround check if this MC is label based from the TRS +// labels.add(LabelBasedNodeTypeRepresentationStrategy.LABELSTRATEGY_PREFIX+alias); return labels; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategy.java index 713b665eb..d6bfc0dcb 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/LabelBasedNodeTypeRepresentationStrategy.java @@ -72,11 +72,15 @@ public class LabelBasedNodeTypeRepresentationStrategy implements NodeTypeReprese * as the primary SDN marker Label. */ private void addLabelsForEntityHierarchy(Node state, StoredEntityType type) { + cypherHelper.setLabelsOnNode(state.getId(), getAllHierarchyLabelsForType(type)); + } + + private Set getAllHierarchyLabelsForType(StoredEntityType type) { String alias = type.getAlias().toString(); Set labels = collectSuperTypeLabels(type, new LinkedHashSet()); labels.add(alias); labels.add(LABELSTRATEGY_PREFIX + alias); - cypherHelper.setLabelsOnNode(state.getId(), labels); + return labels; } private Set collectSuperTypeLabels(StoredEntityType type, Set labels) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java index 6255cfe32..e333d2a80 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/template/Neo4jOperations.java @@ -81,7 +81,7 @@ public interface Neo4jOperations { * creates the node uniquely or returns an existing node with the same index-key-value combination. * properties are used to initialize the node. */ - Node getOrCreateNode(String index, String key, Object value, Map properties); + Node getOrCreateNode(String index, String key, Object value, Map properties, Collection labels); /** * creates the node uniquely or returns an existing node with the same label-key-value combination. diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/DelegatingGraphDatabaseTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/DelegatingGraphDatabaseTests.java index 6fb9a1b95..1bae355d2 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/DelegatingGraphDatabaseTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/support/DelegatingGraphDatabaseTests.java @@ -51,8 +51,8 @@ public class DelegatingGraphDatabaseTests { @Test public void testGetOrCreateNode() throws Exception { try (Transaction tx = graphDatabase.beginTx()) { - final Node node = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David")); - final Node node2 = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David")); + final Node node = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David"), null); + final Node node2 = graphDatabase.getOrCreateNode("user", "name", "David", map("name", "David"), null); assertEquals("David",node.getProperty("name")); assertEquals(node,node2); assertEquals(node,gdb.index().forNodes("user").get("name","David").getSingle());