DATAGRAPH-436 Use case for @Labels (first stab at this)
This commit is contained in:
@@ -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]);
|
||||
|
||||
@@ -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<String,Object> properties) {
|
||||
public Node getOrCreateNode(String indexName, String key, Object value, final Map<String, Object> properties, Collection<String> labels) {
|
||||
if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null");
|
||||
final RestIndex<Node> 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
|
||||
|
||||
@@ -217,6 +217,12 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>${logback}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -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<String,Object> properties);
|
||||
Node getOrCreateNode(String indexName, String key, Object value, final Map<String, Object> properties, Collection<String> labels);
|
||||
|
||||
/**
|
||||
* @param id relationship id
|
||||
|
||||
@@ -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<String> oldLabels = getLabels(node);
|
||||
for (String newLabel : (Iterable<String>) 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<String> getLabels(Node state) {
|
||||
Set<String> labels = new TreeSet<>();
|
||||
for (Label label : state.getLabels()) {
|
||||
labels.add(label.name());
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDefaultValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<String,Object> nodeProperties) {
|
||||
public Node getOrCreateNode(String indexName, String key, Object value, final Map<String, Object> nodeProperties, final Collection<String> 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<String, Object> _) {
|
||||
setProperties(node,nodeProperties);
|
||||
setLabels(node,labels);
|
||||
}
|
||||
};
|
||||
return factory.getOrCreate(key, value);
|
||||
}
|
||||
|
||||
private Node setLabels(Node node, Collection<String> 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<String, Object> properties) {
|
||||
if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null");
|
||||
|
||||
@@ -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<String, Object> properties) {
|
||||
return getGraphDatabase().getOrCreateNode(index, key, value, properties);
|
||||
public Node getOrCreateNode(String index, String key, Object value, final Map<String, Object> properties, Collection<String> 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.<String,Object>emptyMap(), persistentEntity.getAllLabels());
|
||||
} else {
|
||||
if (value instanceof Number && indexInfo.isNumeric()) value = ValueContext.numeric((Number) value);
|
||||
return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String, Object>emptyMap());
|
||||
return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String, Object>emptyMap(), persistentEntity.getAllLabels());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ public class EntityStateHandler {
|
||||
if (indexInfo.isLabelBased()) {
|
||||
return graphDatabase.merge(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap(), persistentEntity.getAllLabels());
|
||||
} else {
|
||||
return graphDatabase.getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap());
|
||||
return graphDatabase.getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.<String,Object>emptyMap(),persistentEntity.getAllLabels());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
//TODO re-enable after SDC update setStrict(true);
|
||||
setStrict(true);
|
||||
}
|
||||
|
||||
private boolean isRelationshipEntityType(Class<?> type) {
|
||||
|
||||
@@ -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<T> extends BasicPersistentEntity<T, Neo4j
|
||||
final Set<String> labels = collectSuperTypeLabels(storedType, new LinkedHashSet<String>());
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> getAllHierarchyLabelsForType(StoredEntityType type) {
|
||||
String alias = type.getAlias().toString();
|
||||
Set<String> labels = collectSuperTypeLabels(type, new LinkedHashSet<String>());
|
||||
labels.add(alias);
|
||||
labels.add(LABELSTRATEGY_PREFIX + alias);
|
||||
cypherHelper.setLabelsOnNode(state.getId(), labels);
|
||||
return labels;
|
||||
}
|
||||
|
||||
private Set<String> collectSuperTypeLabels(StoredEntityType type, Set<String> labels) {
|
||||
|
||||
@@ -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<String, Object> properties);
|
||||
Node getOrCreateNode(String index, String key, Object value, Map<String, Object> properties, Collection<String> labels);
|
||||
|
||||
/**
|
||||
* creates the node uniquely or returns an existing node with the same label-key-value combination.
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user