From 2f724d607e3e90a3919ecd9dbfb8e881af5516d2 Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Mon, 10 Mar 2014 23:27:20 +0100 Subject: [PATCH] DATAGRAPH-433 Handle Uniqueness for Label based Entities * added merge to Neo4jTemplate, Database * lazy determination of the index-label-name * initialize storedEntityType early on, update with hierarchy information later * label handling (correctly add additional labels to the node entity on create) * uniqueness handling (choose merge in case of unique properties for creation) * collecting of labels of a Neo4jPersistentEntity as getAllLabels() --- pom.xml | 2 +- .../neo4j/rest/SpringRestGraphDatabase.java | 27 +++- .../data/neo4j/config/Neo4jConfiguration.java | 3 +- .../data/neo4j/core/GraphDatabase.java | 7 +- .../NodeDelegatingFieldAccessorFactory.java | 5 - .../PropertyFieldAccessorFactory.java | 14 +- .../SchemaIndexingFieldAccessorFactory.java | 111 ---------------- ...gPropertyFieldAccessorListenerFactory.java | 122 ------------------ .../data/neo4j/mapping/IndexInfo.java | 16 +-- .../neo4j/mapping/Neo4jPersistentEntity.java | 4 + .../repository/AbstractGraphRepository.java | 2 +- .../support/DelegatingGraphDatabase.java | 30 ++++- .../data/neo4j/support/Neo4jTemplate.java | 31 ++++- .../support/mapping/EntityIndexCreator.java | 7 +- .../support/mapping/EntityStateHandler.java | 17 ++- .../support/mapping/Neo4jMappingContext.java | 21 +-- .../mapping/Neo4jPersistentEntityImpl.java | 51 +++++++- .../support/mapping/StoredEntityType.java | 4 +- .../support/schema/SchemaIndexProvider.java | 34 ++++- ...elBasedNodeTypeRepresentationStrategy.java | 3 +- ...ferenceNodeTypeRepresentationStrategy.java | 2 +- .../data/neo4j/template/Neo4jOperations.java | 12 ++ .../mapping/Neo4jPersistentTestBase.java | 2 +- .../support/DelegatingGraphDatabaseTests.java | 47 ++++++- .../template/FullNeo4jTemplateTests.java | 7 +- .../neo4j/template/Neo4jTemplateApiTests.java | 5 +- .../Neo4jTemplateApiTransactionTests.java | 7 +- .../neo4j/template/Neo4jTemplateTests.java | 4 +- .../UniqueLegacyIndexBasedEntityTests.java | 4 +- .../UniqueSchemaBasedEntityTests.java | 2 +- 30 files changed, 290 insertions(+), 313 deletions(-) delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingFieldAccessorFactory.java delete mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingPropertyFieldAccessorListenerFactory.java diff --git a/pom.xml b/pom.xml index dd4cd36bc..73e5f2f10 100644 --- a/pom.xml +++ b/pom.xml @@ -32,7 +32,7 @@ multi spring-data-neo4j - 1.8.0.BUILD-SNAPSHOT + 1.7.0.RELEASE 1.7 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 dcb4c4d05..0a159cf88 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 @@ -34,19 +34,27 @@ import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.support.index.NoSuchIndexException; import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter; import org.springframework.data.neo4j.support.query.QueryEngine; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; import javax.transaction.TransactionManager; +import java.util.Collection; import java.util.Map; +import static org.neo4j.helpers.collection.MapUtil.map; + public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDatabase implements GraphDatabase { static { System.setProperty(Config.CONFIG_BATCH_TRANSACTION,"false"); } + + private static final String[] NO_LABELS = new String[0]; private ConversionService conversionService; private ResultConverter resultConverter; + private SchemaIndexProvider schemaIndexProvider; public SpringRestGraphDatabase( RestAPI api){ super(api); + schemaIndexProvider = new SchemaIndexProvider(this); } public SpringRestGraphDatabase( String uri ) { @@ -58,8 +66,18 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat } @Override - public Node createNode(Map props) { - return super.getRestAPI().createNode(props); + public Node createNode(Map props, Collection labels) { + RestAPI restAPI = super.getRestAPI(); + RestNode node = restAPI.createNode(props); + if (labels!=null && !labels.isEmpty()) { + restAPI.addLabels(node, toLabels(labels)); + } + return node; + } + + private String[] toLabels(Collection labels) { + if (labels==null || labels.isEmpty()) return NO_LABELS; + return labels.toArray(new String[labels.size()]); } @Override @@ -80,6 +98,11 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat return getRestAPI().getOrCreateNode(nodeIndex, key, value, properties); } + @Override + public Node merge(String labelName, String key, Object value, final Map nodeProperties, Collection labels) { + return schemaIndexProvider.merge(labelName,key,value,nodeProperties, labels); + } + @Override public Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map properties) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java index 89b09e4d7..d60558546 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java @@ -242,7 +242,8 @@ public abstract class Neo4jConfiguration { public EntityIndexCreator entityIndexCreator() throws Exception { return new EntityIndexCreator( indexProvider(), - schemaIndexProvider() + schemaIndexProvider(), + nodeTypeRepresentationStrategy().isLabelBased() ); } 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 be9663280..73d09de07 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 @@ -41,8 +41,13 @@ public interface GraphDatabase { /** * creates the node and initializes its properties */ - Node createNode(Map props); + Node createNode(Map props, Collection labels); + /** + * creates the node uniquely or returns an existing node with the same label-key-value combination. + * properties are used to initialize the node. It needs a unique constraint to work correctly. + */ + Node merge(String labelName, String key, Object value, final Map properties, Collection labels); /** * creates the node uniquely or returns an existing node with the same index-key-value combination. * properties are used to initialize the node. diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java index 4af61460a..c21f10e21 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/NodeDelegatingFieldAccessorFactory.java @@ -39,10 +39,6 @@ public class NodeDelegatingFieldAccessorFactory extends DelegatingFieldAccessorF template, new PropertyFieldAccessorFactory(template), new ConvertingNodePropertyFieldAccessorFactory(template)), - /*new SchemaIndexingPropertyFieldAccessorListenerFactory( - template, - new PropertyFieldAccessorFactory(template), - new ConvertingNodePropertyFieldAccessorFactory(template)), */ new ValidatingNodePropertyFieldAccessorListenerFactory(template) ); } @@ -53,7 +49,6 @@ public class NodeDelegatingFieldAccessorFactory extends DelegatingFieldAccessorF new IdFieldAccessorFactory(template), new TransientFieldAccessorFactory(), //TODO Labels new LabelFieldAccessorFactory(template), - new SchemaIndexingFieldAccessorFactory(template), new TraversalFieldAccessorFactory(template), new QueryFieldAccessorFactory(template), new PropertyFieldAccessorFactory(template), diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/PropertyFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/PropertyFieldAccessorFactory.java index 576d842cc..0b773db3b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/PropertyFieldAccessorFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/PropertyFieldAccessorFactory.java @@ -16,8 +16,10 @@ package org.springframework.data.neo4j.fieldaccess; +import org.neo4j.graphdb.ConstraintViolationException; import org.neo4j.graphdb.PropertyContainer; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.neo4j.mapping.MappingPolicy; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.Neo4jTemplate; @@ -67,10 +69,14 @@ public class PropertyFieldAccessorFactory implements FieldAccessorFactory { @Override public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) { final PropertyContainer propertyContainer = template.getPersistentState(entity); - if (newVal==null) { - propertyContainer.removeProperty(propertyName); - } else { - propertyContainer.setProperty(propertyName, newVal); + try { + if (newVal==null) { + propertyContainer.removeProperty(propertyName); + } else { + propertyContainer.setProperty(propertyName, newVal); + } + } catch(ConstraintViolationException cve) { + throw new DataIntegrityViolationException("Unique constraint violated "+property.getOwner().getName()+"."+property.getName()+" new value "+newVal,cve); } return newVal; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingFieldAccessorFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingFieldAccessorFactory.java deleted file mode 100644 index 56f9e7f93..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingFieldAccessorFactory.java +++ /dev/null @@ -1,111 +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.DynamicLabel; -import org.neo4j.graphdb.Label; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.PropertyContainer; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.mapping.model.MappingException; -import org.springframework.data.neo4j.mapping.MappingPolicy; -import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; -import org.springframework.data.neo4j.support.Neo4jTemplate; -import org.springframework.data.neo4j.support.mapping.StoredEntityType; - -import java.util.Set; -import java.util.TreeSet; - -import static org.springframework.data.neo4j.support.DoReturn.doReturn; - -/** - * @author Nicki Watt - * @since 01.03.2014 - */ -public class SchemaIndexingFieldAccessorFactory implements FieldAccessorFactory { - private final Neo4jTemplate template; - - public SchemaIndexingFieldAccessorFactory(Neo4jTemplate template) { - this.template = template; - } - - @Override - public boolean accept(final Neo4jPersistentProperty property) { - return property.isIndexed() && property.getIndexInfo().isLabelBased(); - } - - @Override - public FieldAccessor forField(final Neo4jPersistentProperty property) { - return new SchemaIndexedFieldAccessor(template,property); - } - - public static class SchemaIndexedFieldAccessor extends PropertyFieldAccessorFactory.PropertyFieldAccessor { - - public SchemaIndexedFieldAccessor(Neo4jTemplate template,Neo4jPersistentProperty property) { - super(template,property); - } - - @Override - public boolean isWriteable(Object entity) { - return super.isWriteable(entity); - } - - @Override - public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) { - final PropertyContainer state = template.getPersistentState(entity); - if (!(state instanceof Node)) { - throw new IllegalArgumentException("not expecting to deal with non node property"); - } - - applyMissingSchemaIndexLabels(entity,(Node)state); - checkForUniqueViolation(entity, newVal, (Node)state); - return super.setValue(entity,newVal,mappingPolicy); - } - - private void checkForUniqueViolation(Object entity,Object newVal, Node stateToBeSaved) { - StoredEntityType set = template.getStoredEntityType(entity); - if (newVal != null && property.isUnique()) { - Object existingUniqueEntity = template.findUniqueEntity(set.getEntity().getType(),property.getNeo4jPropertyName(),newVal); - if (existingUniqueEntity == null) return; - final Node existingUniqueState = (Node)template.getPersistentState(existingUniqueEntity); - if (existingUniqueState.equals(stateToBeSaved)) return; - throw new DataIntegrityViolationException("Unique property "+property+" was to be set to duplicate value "+newVal); - } - } - - private void applyMissingSchemaIndexLabels(Object entity,Node state) { - // TODO - This logic should rather be done once when the - // entity is persisted for the first time rather than - // on each update .... - StoredEntityType set = template.getStoredEntityType(entity); - if (set != null) { - applyMissingSchemaIndexLabels(state, set); - } - } - - private void applyMissingSchemaIndexLabels(Node node, StoredEntityType set) { - for (StoredEntityType ancestorSet : set.getSuperTypes()) { - applyMissingSchemaIndexLabels(node, ancestorSet); - } - Label label = DynamicLabel.label( (String)set.getAlias()); - if (!node.hasLabel(label)) - node.addLabel(label); - } - - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingPropertyFieldAccessorListenerFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingPropertyFieldAccessorListenerFactory.java deleted file mode 100644 index cfeacbbb2..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingPropertyFieldAccessorListenerFactory.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright 2014 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.neo4j.graphdb.index.Index; -import org.neo4j.graphdb.schema.IndexDefinition; -import org.neo4j.index.lucene.ValueContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; -import org.springframework.data.neo4j.support.Neo4jTemplate; -import org.springframework.data.neo4j.support.mapping.StoredEntityType; - -import java.util.Arrays; - - -public class SchemaIndexingPropertyFieldAccessorListenerFactory implements FieldAccessorListenerFactory { - - private final PropertyFieldAccessorFactory propertyFieldAccessorFactory; - private final ConvertingNodePropertyFieldAccessorFactory convertingNodePropertyFieldAccessorFactory; - private final Neo4jTemplate template; - - public SchemaIndexingPropertyFieldAccessorListenerFactory(final Neo4jTemplate template, final PropertyFieldAccessorFactory propertyFieldAccessorFactory, final ConvertingNodePropertyFieldAccessorFactory convertingNodePropertyFieldAccessorFactory) { - this.template = template; - this.propertyFieldAccessorFactory = propertyFieldAccessorFactory; - this.convertingNodePropertyFieldAccessorFactory = convertingNodePropertyFieldAccessorFactory; - } - - @Override - public boolean accept(final Neo4jPersistentProperty property) { - return isPropertyField(property) && property.isIndexed() && property.getIndexInfo().isLabelBased(); - } - - - private boolean isPropertyField(final Neo4jPersistentProperty property) { - return propertyFieldAccessorFactory.accept(property) || convertingNodePropertyFieldAccessorFactory.accept(property); - } - - @Override - public FieldAccessListener forField(Neo4jPersistentProperty property) { - return new SchemaIndexingPropertyFieldAccessorListener(property, template); - } - - - /** - * @author Nicki Watt - * @since 09.02.2014 - */ - public static class SchemaIndexingPropertyFieldAccessorListener implements FieldAccessListener { - - private final static Logger log = LoggerFactory.getLogger(SchemaIndexingPropertyFieldAccessorListener.class); - - private final Neo4jPersistentProperty property; - private final Neo4jTemplate template; - - public SchemaIndexingPropertyFieldAccessorListener(final Neo4jPersistentProperty property, Neo4jTemplate template) { - this.property = property; - this.template = template; - } - - @Override - public void valueChanged(Object entity, Object oldVal, Object newVal) { - final PropertyContainer state = template.getPersistentState(entity); - if (!(state instanceof Node)) { - throw new IllegalArgumentException("not expecting to deal with non node property"); - } - - applyMissingSchemaIndexLabels(entity,(Node)state); - checkForUniqueViolation(entity, newVal, (Node)state); - } - - private void checkForUniqueViolation(Object entity,Object newVal, Node stateToBeSaved) { - StoredEntityType set = template.getStoredEntityType(entity); - if (newVal != null && property.isUnique()) { - Object existingUniqueEntity = template.findUniqueEntity(set.getEntity().getType(),property.getNeo4jPropertyName(),newVal); - if (existingUniqueEntity == null) return; - final Node existingUniqueState = (Node)template.getPersistentState(existingUniqueEntity); - if (existingUniqueState.equals(stateToBeSaved)) return; - throw new DataIntegrityViolationException("Unique property "+property+" was to be set to duplicate value "+newVal); - } - } - - private void applyMissingSchemaIndexLabels(Object entity,Node state) { - // TODO - This logic should rather be done once when the - // entity is persisted for the first time rather than - // on each update .... - StoredEntityType set = template.getStoredEntityType(entity); - if (set != null) { - applyMissingSchemaIndexLabels(state, set); - } - } - - private void applyMissingSchemaIndexLabels(Node node, StoredEntityType set) { - for (StoredEntityType ancestorSet : set.getSuperTypes()) { - applyMissingSchemaIndexLabels(node, ancestorSet); - } - Label label = DynamicLabel.label( (String)set.getAlias()); - if (!node.hasLabel(label)) - node.addLabel(label); - } - - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java index 46d5128dc..6b4fcd5b3 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java @@ -32,23 +32,19 @@ public class IndexInfo { private String indexKey; private final boolean unique; private boolean numeric; + private Indexed annotation; + private Neo4jPersistentProperty property; public IndexInfo(Indexed annotation, Neo4jPersistentProperty property) { + this.annotation = annotation; + this.property = property; this.indexType = annotation.indexType(); - this.indexName = isLabelBased() ? determineLabelIndexName(annotation, property) : determineIndexName(annotation, property); fieldName = annotation.fieldName(); this.indexKey = fieldName.isEmpty() ? property.getNeo4jPropertyName() : fieldName; unique = annotation.unique(); level = annotation.level(); numeric = annotation.numeric(); - verify(property); - } - - private void verify(Neo4jPersistentProperty property) { -// if (isLabelBased() && numeric) { -// throw new MappingException("No numeric indexing and range queries currently supported for label based indexes, property: " + property.getOwner().getName()+"."+property.getName()); -// } } private String determineLabelIndexName(Indexed annotation, Neo4jPersistentProperty property) { @@ -94,7 +90,11 @@ public class IndexInfo { return indexType.isLabelBased(); } + // lazy because of deferred persistent entity hierarchy determination on registration public String getIndexName() { + if (indexName == null) { + this.indexName = isLabelBased() ? determineLabelIndexName(annotation, property) : determineIndexName(annotation, property); + } return indexName; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentEntity.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentEntity.java index 9afb62de7..e942e8101 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentEntity.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4jPersistentEntity.java @@ -20,6 +20,8 @@ import org.neo4j.graphdb.PropertyContainer; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.neo4j.support.mapping.StoredEntityType; +import java.util.Collection; + /** * Interface for Neo4J specific {@link PersistentEntity}. * @@ -46,4 +48,6 @@ public interface Neo4jPersistentEntity extends PersistentEntity getAllLabels(); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java index d1eb3000a..d7ebb4061 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/AbstractGraphRepository.java @@ -274,7 +274,7 @@ public abstract class AbstractGraphRepository im Neo4jPersistentEntity persistentEntity = template.getEntityType(clazz).getEntity(); Neo4jPersistentProperty persistentProperty = (Neo4jPersistentProperty)persistentEntity.getPersistentProperty(property); if (persistentProperty.getIndexInfo() == null || !persistentProperty.getIndexInfo().isLabelBased() ) { - throw new IllegalArgumentException(format("property {} is not schema indexed",property)); + throw new IllegalArgumentException(format("property %s.%s is not schema indexed",persistentEntity.getName(),property)); } Map params = new HashMap(); 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 cb0c129e0..6a5b49e80 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 @@ -40,16 +40,16 @@ import org.springframework.data.neo4j.support.index.NoSuchIndexException; import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter; import org.springframework.data.neo4j.support.query.CypherQueryEngine; import org.springframework.data.neo4j.support.query.QueryEngine; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.TransactionManager; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; + +import static org.neo4j.helpers.collection.MapUtil.map; /** * @author mh @@ -58,12 +58,13 @@ import java.util.Set; public class DelegatingGraphDatabase implements GraphDatabase { private static final Logger log = LoggerFactory.getLogger(DelegatingGraphDatabase.class); + private static final Label[] NO_LABELS = new Label[0]; + private final SchemaIndexProvider schemaIndexProvider; protected GraphDatabaseService delegate; private ConversionService conversionService; private ResultConverter resultConverter; private volatile QueryEngine cypherQueryEngine; - private long referenceNode = -2; public DelegatingGraphDatabase(final GraphDatabaseService delegate) { this(delegate,null); @@ -71,6 +72,7 @@ public class DelegatingGraphDatabase implements GraphDatabase { public DelegatingGraphDatabase(final GraphDatabaseService delegate, ResultConverter resultConverter) { this.delegate = delegate; this.resultConverter = resultConverter; + this.schemaIndexProvider = new SchemaIndexProvider(this); } public void setConversionService(ConversionService conversionService) { @@ -102,8 +104,18 @@ public class DelegatingGraphDatabase implements GraphDatabase { } @Override - public Node createNode(Map props) { - return setProperties(delegate.createNode(), props); + public Node createNode(Map props, Collection labels) { + return setProperties(delegate.createNode(toLabels(labels)), props); + } + + private Label[] toLabels(Collection labels) { + if (labels==null || labels.isEmpty()) return NO_LABELS; + Label[] labelArray = new Label[labels.size()]; + int i=0; + for (String label : labels) { + labelArray[i++]= DynamicLabel.label(label); + } + return labelArray; } private T setProperties(T primitive, Map properties) { @@ -309,6 +321,10 @@ public class DelegatingGraphDatabase implements GraphDatabase { } } + public Node merge(String labelName, String key, Object value, final Map nodeProperties, Collection labels) { + return schemaIndexProvider.merge(labelName,key,value,nodeProperties,labels); + } + public Node getOrCreateNode(String indexName, String key, Object value, final Map nodeProperties) { 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); 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 f414ec6b5..14f5601d3 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 @@ -65,6 +65,7 @@ import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import javax.validation.Validator; +import java.util.Collection; import java.util.Collections; import java.util.Map; @@ -271,16 +272,23 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware { */ @Override public Node createNode() { - return getGraphDatabase().createNode(null); + return createNode(null, null); } /** - * creates the node uniquely or returns an existing node with the same index-key-value combination. * properties are used to initialize the node. */ @Override public Node createNode(final Map properties) { - return getGraphDatabase().createNode(properties); + return createNode(properties, null); + } + + /** + * properties are used to initialize the node. + */ + @Override + public Node createNode(final Map properties,Collection labels) { + return getGraphDatabase().createNode(properties, labels); } /** @@ -292,6 +300,15 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware { return getGraphDatabase().getOrCreateNode(index, key, value, properties); } + /** + * creates the node uniquely or returns an existing node with the same label-key-value combination. + * properties are used to initialize the node. + */ + @Override + public Node merge(String label, String key, Object value, final Map properties, Collection labels) { + return getGraphDatabase().merge(label, key, value, properties, labels); + } + @Override public T createNodeAs(Class target, Map properties) { final Node node = createNode(properties); @@ -718,8 +735,12 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware { Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY); if (value == null) return createNode(); final IndexInfo indexInfo = uniqueProperty.getIndexInfo(); - if (value instanceof Number && indexInfo.isNumeric()) value = ValueContext.numeric((Number) value); - return getGraphDatabase().getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap()); + if (indexInfo.isLabelBased()) { + 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()); + } } @Override diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityIndexCreator.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityIndexCreator.java index 69ce0b45f..af8c6dcd5 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityIndexCreator.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/EntityIndexCreator.java @@ -29,10 +29,15 @@ import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; public class EntityIndexCreator { private IndexProvider indexProvider; private SchemaIndexProvider schemaIndexProvider; + private boolean labelBased = true; public EntityIndexCreator(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider) { + this(indexProvider, schemaIndexProvider,true); + } + public EntityIndexCreator(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider, boolean labelBased) { this.indexProvider = indexProvider; this.schemaIndexProvider = schemaIndexProvider; + this.labelBased = labelBased; } public void ensureEntityIndexes(Neo4jPersistentEntity entity) { @@ -48,7 +53,7 @@ public class EntityIndexCreator { }); // Pass 2 - do everything else - indexProvider.getIndex(entity, null, IndexType.SIMPLE); + if (!labelBased) indexProvider.getIndex(entity, null, IndexType.SIMPLE); entity.doWithProperties(new PropertyHandler() { @Override public void doWithPersistentProperty(Neo4jPersistentProperty property) { 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 0ced33c78..5a82d6e54 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 @@ -132,8 +132,8 @@ public class EntityStateHandler { final MappingPolicy mappingPolicy = persistentEntity.getMappingPolicy(); // todo observe load policy if (persistentEntity.isNodeEntity()) { - if (persistentEntity.isUnique()) return (S)createUniqueNode(persistentEntity.getUniqueProperty(),entity); - return (S) graphDatabase.createNode(null); + if (persistentEntity.isUnique()) return (S)createUniqueNode(persistentEntity,entity); + return createNode(persistentEntity); } if (persistentEntity.isRelationshipEntity()) { return getOrCreateRelationship(entity, persistentEntity, annotationProvidedRelationshipType ); @@ -141,11 +141,20 @@ public class EntityStateHandler { throw new IllegalArgumentException("The entity " + persistentEntity.getEntityName() + " has to be either annotated with @NodeEntity or @RelationshipEntity"); } - private Node createUniqueNode(Neo4jPersistentProperty uniqueProperty, Object entity) { + private S createNode(Neo4jPersistentEntityImpl persistentEntity) { + return (S) graphDatabase.createNode(null,persistentEntity.getAllLabels()); + } + + private Node createUniqueNode(Neo4jPersistentEntityImpl persistentEntity, Object entity) { + Neo4jPersistentProperty uniqueProperty = persistentEntity.getUniqueProperty(); final IndexInfo indexInfo = uniqueProperty.getIndexInfo(); final Object value = uniqueProperty.getValueFromEntity(entity, MappingPolicy.MAP_FIELD_DIRECT_POLICY); if (value==null) throw new MappingException("Error creating "+uniqueProperty.getOwner().getName()+" with "+entity+" unique property "+uniqueProperty.getName()+" has null value"); - return graphDatabase.getOrCreateNode(indexInfo.getIndexName(), indexInfo.getIndexKey(), value, Collections.emptyMap()); + 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()); + } } @SuppressWarnings("unchecked") 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 4f15b43f2..05d6ada64 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 @@ -56,10 +56,10 @@ public class Neo4jMappingContext extends AbstractMappingContext Neo4jPersistentEntityImpl createPersistentEntity(TypeInformation typeInformation) { final Class type = typeInformation.getType(); if (type.isAnnotationPresent(NodeEntity.class)) { - return new Neo4jPersistentEntityImpl(typeInformation); + return new Neo4jPersistentEntityImpl(typeInformation,entityAlias); } if (type.isAnnotationPresent(RelationshipEntity.class)) { - return new Neo4jPersistentEntityImpl(typeInformation); + return new Neo4jPersistentEntityImpl(typeInformation,entityAlias); } throw new InvalidEntityTypeException("Type " + type + " is neither a @NodeEntity nor a @RelationshipEntity"); } @@ -73,16 +73,21 @@ public class Neo4jMappingContext extends AbstractMappingContext entity, Collection> superTypeEntities) { - entity.updateStoredType(new StoredEntityType(entity, superTypeEntities, entityAlias)); + entity.updateStoredType(superTypeEntities); if (entityIndexCreator!=null) entityIndexCreator.ensureEntityIndexes(entity); } private List> addSuperTypes(Neo4jPersistentEntity entity) { - List> entities=new ArrayList>(); - final Class type = entity.getType(); - entities.addAll(addPersistentEntityWithCheck(type.getSuperclass())); - for (Class anInterface : type.getInterfaces()) { - entities.addAll(addPersistentEntityWithCheck(anInterface)); + List> entities=new ArrayList<>(); + Class type = entity.getType(); + Collection typesToAdd = new LinkedHashSet<>(); + while (type != null) { + typesToAdd.add(type.getSuperclass()); + typesToAdd.addAll(Arrays.asList(type.getInterfaces())); + type = type.getSuperclass(); + } + for (Class superType : typesToAdd) { + entities.addAll(addPersistentEntityWithCheck(superType)); } return entities; } 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 cd05f7594..911ab5dc0 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 @@ -17,14 +17,15 @@ package org.springframework.data.neo4j.support.mapping; import java.lang.annotation.Annotation; -import java.util.IdentityHashMap; -import java.util.Map; +import java.util.*; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.PropertyContainer; import org.neo4j.graphdb.Relationship; import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.MappingException; import org.springframework.data.neo4j.annotation.NodeEntity; @@ -47,23 +48,29 @@ public class Neo4jPersistentEntityImpl extends BasicPersistentEntity labels; /** * Creates a new {@link Neo4jPersistentEntityImpl} instance. - * + * * @param information must not be {@literal null}. + * @param entityAlias */ - public Neo4jPersistentEntityImpl(TypeInformation information) { + public Neo4jPersistentEntityImpl(TypeInformation information, EntityAlias entityAlias) { super(information); + this.entityAlias = entityAlias; for (Annotation annotation : information.getType().getAnnotations()) { annotations.put(annotation.annotationType(),annotation); } managed = ManagedEntity.class.isAssignableFrom(information.getType()); shouldUseShortNames = shouldUseShortNames(); + updateStoredType(null); } - void updateStoredType(StoredEntityType storedType) { - this.storedType = storedType; + void updateStoredType(Collection> superTypeEntities) { + this.storedType = new StoredEntityType(this,superTypeEntities,entityAlias); + this.labels = computeLabels(); } @Override @@ -234,4 +241,36 @@ public class Neo4jPersistentEntityImpl extends BasicPersistentEntity getAllLabels() { + return labels; + } + + private Set computeLabels() { + String alias = storedType.getAlias().toString(); + final Set labels = collectSuperTypeLabels(storedType, new LinkedHashSet()); + labels.add(alias); + doWithProperties(new PropertyHandler() { + @Override + public void doWithPersistentProperty(Neo4jPersistentProperty persistentProperty) { + if (persistentProperty.isIndexed()) { + IndexInfo indexInfo = persistentProperty.getIndexInfo(); + if (indexInfo.isLabelBased()) { + labels.add(indexInfo.getIndexName()); + } + } + } + }); + return labels; + } + + private Set collectSuperTypeLabels(StoredEntityType type, Set labels) { + if (type==null) return labels; + for (StoredEntityType superType : type.getSuperTypes()) { + labels.add(superType.getAlias().toString()); + collectSuperTypeLabels(superType, labels); + } + return labels; + } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/StoredEntityType.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/StoredEntityType.java index c7444fc21..7d2a86292 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/StoredEntityType.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/StoredEntityType.java @@ -20,6 +20,7 @@ import org.springframework.data.util.TypeInformation; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; /** * @author mh @@ -59,7 +60,8 @@ public class StoredEntityType { } private Collection collectSuperTypes(Collection> superTypeEntities) { - Collection result=new ArrayList(superTypeEntities.size()); + if (superTypeEntities==null) return Collections.emptyList(); + Collection result=new ArrayList<>(superTypeEntities.size()); for (Neo4jPersistentEntity superTypeEntity : superTypeEntities) { result.add(superTypeEntity.getEntityType()); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java index 6c5496335..51a8f4d34 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java @@ -1,6 +1,7 @@ package org.springframework.data.neo4j.support.schema; -import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.Node; +import org.neo4j.helpers.collection.MapUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.neo4j.annotation.QueryType; @@ -11,6 +12,9 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.query.QueryEngine; +import java.util.Collection; +import java.util.Map; + import static org.neo4j.helpers.collection.MapUtil.map; /** @@ -31,7 +35,12 @@ public class SchemaIndexProvider { public void createIndex(Neo4jPersistentProperty property) { String label = getLabel(property); String prop = getName(property); - String query = indexQuery(label, prop, property.getIndexInfo().isUnique()); + boolean unique = property.getIndexInfo().isUnique(); + createIndex(label, prop, unique); + } + + public void createIndex(String label, String prop, boolean unique) { + String query = createIndexQuery(label, prop, unique); if (logger.isDebugEnabled()) logger.debug(query); cypher.query(query, null); } @@ -62,11 +71,30 @@ public class SchemaIndexProvider { return "MATCH (n:`"+label+"`) RETURN n"; } + public Node merge(String labelName, String key, Object value, final Map nodeProperties, Collection labels) { + if (labelName ==null || key == null || value==null) throw new IllegalArgumentException("Label "+ labelName +" key "+key+" and value must not be null"); + Map props = nodeProperties.containsKey(key) ? nodeProperties : MapUtil.copyAndPut(nodeProperties, key, value); + Map params = map("props", props, "value", value); + return cypher.query(mergeQuery(labelName, key,labels), params).to(Node.class).single(); + } + + private String mergeQuery(String labelName, String key, Collection labels) { + StringBuilder setLabels = new StringBuilder(); + if (labels!=null) { + for (String label : labels) { + if (label.equals(labelName)) continue; + setLabels.append("SET n:").append(label).append(" "); + } + } + return "MERGE (n:`"+labelName+"` {`"+key+"`: {value}}) ON CREATE SET n={props} "+setLabels+" return n"; + } + + private String findByLabelAndPropertyQuery(String label, String prop) { return "MATCH (n:`"+label+"` {`"+prop+"`:{value}}) RETURN n"; } - private String indexQuery(String label, String prop, boolean unique) { + private String createIndexQuery(String label, String prop, boolean unique) { if (unique) { return "CREATE CONSTRAINT ON (n:`"+ label +"`) ASSERT n.`"+ prop +"` IS UNIQUE"; } 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 1356343f3..58ed3ab56 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 @@ -30,6 +30,7 @@ import org.springframework.data.neo4j.support.mapping.WrappedIterableClosableIte import org.springframework.data.neo4j.support.query.QueryEngine; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; /** @@ -76,7 +77,7 @@ public class LabelBasedNodeTypeRepresentationStrategy implements NodeTypeReprese */ private void addLabelsForEntityHierarchy(Node state, StoredEntityType type) { String alias = type.getAlias().toString(); - Set labels = collectSuperTypeLabels(type, new HashSet()); + Set labels = collectSuperTypeLabels(type, new LinkedHashSet()); labels.add(alias); labels.add(LABELSTRATEGY_PREFIX + alias); cypherHelper.setLabelsOnNode(state.getId(), labels); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java index 803d4a32b..b219ef2cc 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/SubReferenceNodeTypeRepresentationStrategy.java @@ -248,7 +248,7 @@ public class SubReferenceNodeTypeRepresentationStrategy implements NodeTypeRepre return singleRelationship.getOtherNode(fromNode); } - Node otherNode = graphDatabase.createNode(null); + Node otherNode = graphDatabase.createNode(null,null); if (direction == Direction.OUTGOING) fromNode.createRelationshipTo(otherNode, type); 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 7de49f138..4ee56d001 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 @@ -32,6 +32,7 @@ import org.springframework.data.neo4j.mapping.MappingPolicy; import org.springframework.data.neo4j.repository.GraphRepository; import org.springframework.data.neo4j.support.query.QueryEngine; +import java.util.Collection; import java.util.Map; /** @@ -70,6 +71,11 @@ public interface Neo4jOperations { */ Node createNode(Map properties); + /** + * Creates a node with the given properties and labels + */ + Node createNode(final Map properties,Collection labels); + Node createNode(); /** @@ -78,6 +84,12 @@ public interface Neo4jOperations { */ Node getOrCreateNode(String index, String key, Object value, Map properties); + /** + * creates the node uniquely or returns an existing node with the same label-key-value combination. + * properties are used to initialize the node. + */ + Node merge(String label, String key, Object value, Map properties, Collection labels); + /** * Creates a node mapped by the given entity class * @param target mapped entity class or Node.class diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java index 3285a72f8..26930d49b 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/mapping/Neo4jPersistentTestBase.java @@ -134,7 +134,7 @@ public class Neo4jPersistentTestBase { factoryBean.setNodeEntityStateFactory(nodeEntityStateFactory); factoryBean.setRelationshipEntityStateFactory(relationshipEntityStateFactory); - mappingContext.setEntityIndexCreator(new EntityIndexCreator(new IndexProviderImpl(graphDatabase), new SchemaIndexProvider(graphDatabase))); + mappingContext.setEntityIndexCreator(new EntityIndexCreator(new IndexProviderImpl(graphDatabase), new SchemaIndexProvider(graphDatabase),true)); mappingContext.setSimpleTypeHolder(null); setBasePackage(mappingContext); 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 97beab193..6fb9a1b95 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 @@ -18,13 +18,14 @@ package org.springframework.data.neo4j.support; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.neo4j.graphdb.GraphDatabaseService; -import org.neo4j.graphdb.Node; -import org.neo4j.graphdb.Relationship; -import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.*; +import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.test.TestGraphDatabaseFactory; +import org.springframework.data.neo4j.support.schema.SchemaIndexProvider; +import static java.util.Arrays.asList; import static org.junit.Assert.assertEquals; +import static org.neo4j.helpers.collection.IteratorUtil.singleOrNull; import static org.neo4j.helpers.collection.MapUtil.map; /** @@ -59,11 +60,45 @@ public class DelegatingGraphDatabaseTests { } } + @Test + public void mergeNode() throws Exception { + try (Transaction tx = graphDatabase.beginTx()) { + new SchemaIndexProvider(graphDatabase).createIndex("user","name",true); + tx.success(); + } + try (Transaction tx = graphDatabase.beginTx()) { + final Node node = graphDatabase.merge("user", "name", "David", map("name", "David"), null); + final Node node2 = graphDatabase.merge("user", "name", "David", map("name", "David"), null); + assertEquals("David",node.getProperty("name")); + assertEquals(node,node2); + assertEquals(node,singleOrNull(gdb.findNodesByLabelAndProperty(DynamicLabel.label("user"), "name", "David"))); + tx.success(); + } + } + + @Test + public void mergeNodeWithLabel() throws Exception { + try (Transaction tx = graphDatabase.beginTx()) { + new SchemaIndexProvider(graphDatabase).createIndex("user","name",true); + tx.success(); + } + try (Transaction tx = graphDatabase.beginTx()) { + final Node node = graphDatabase.merge("user", "name", "David", map("name", "David"), asList("person")); + assertEquals("David",node.getProperty("name")); + assertEquals(2, IteratorUtil.count(node.getLabels())); + for (Label label : node.getLabels()) { + assertEquals(true, asList("user", "person").contains(label.name())); + } + assertEquals(node,singleOrNull(gdb.findNodesByLabelAndProperty(DynamicLabel.label("user"), "name", "David"))); + tx.success(); + } + } + @Test public void testGetOrCreateRelationship() throws Exception { try (Transaction tx = graphDatabase.beginTx()) { - final Node david = graphDatabase.createNode(map("name", "David")); - final Node michael = graphDatabase.createNode(map("name", "Michael")); + final Node david = graphDatabase.createNode(map("name", "David"), asList("Person")); + final Node michael = graphDatabase.createNode(map("name", "Michael"), asList("Person")); final Relationship rel1 = graphDatabase.getOrCreateRelationship("knows", "whom", "david_michael", david, michael, "KNOWS", map("whom", "david_michael")); final Relationship rel2 = graphDatabase.getOrCreateRelationship("knows", "whom", "david_michael", david, michael, "KNOWS", map("whom", "david_michael")); assertEquals("david_michael",rel1.getProperty("whom")); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/FullNeo4jTemplateTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/FullNeo4jTemplateTests.java index 60c1054b3..048b4174e 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/FullNeo4jTemplateTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/FullNeo4jTemplateTests.java @@ -47,6 +47,7 @@ import org.springframework.transaction.support.TransactionTemplate; import java.util.Iterator; +import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.*; @@ -96,9 +97,9 @@ public class FullNeo4jTemplateTests { new TransactionTemplate(neo4jTransactionManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { - node0 = graphDatabase.createNode(map("name", "node0")); + node0 = graphDatabase.createNode(map("name", "node0"), asList("Node")); graphDatabase.createIndex(Node.class, "node", IndexType.SIMPLE).add(node0, "name", "node0"); - node1 = graphDatabase.createNode(map("name", "node1")); + node1 = graphDatabase.createNode(map("name", "node1"), asList("Node")); relationship1 = node0.createRelationshipTo(node1, KNOWS); relationship1.setProperty("name", "rel1"); graphDatabase.createIndex(Relationship.class, "relationship", IndexType.SIMPLE).add(relationship1, "name", "rel1"); @@ -185,7 +186,7 @@ public class FullNeo4jTemplateTests { template.exec(new GraphCallback.WithoutResult() { @Override public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception { - graph.createNode(null); + graph.createNode(null, labels); } }); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTests.java index c338eaafe..c60af2d6f 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTests.java @@ -42,6 +42,7 @@ import org.springframework.transaction.support.TransactionTemplate; import java.io.IOException; import java.util.Iterator; +import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.*; @@ -89,9 +90,9 @@ public class Neo4jTemplateApiTests { new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { - node0 = graphDatabase.createNode(map("name", "node0")); + node0 = graphDatabase.createNode(map("name", "node0"), asList("Node")); graphDatabase.createIndex(Node.class, "node", IndexType.SIMPLE).add(node0, "name", "node0"); - node1 = graphDatabase.createNode(map("name", "node1")); + node1 = graphDatabase.createNode(map("name", "node1"), asList("Node")); relationship1 = node0.createRelationshipTo(node1, KNOWS); relationship1.setProperty("name", "rel1"); graphDatabase.createIndex(Relationship.class, "relationship", IndexType.SIMPLE).add(relationship1, "name", "rel1"); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTransactionTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTransactionTests.java index 4d51e69d5..485d6a2d9 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTransactionTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateApiTransactionTests.java @@ -35,6 +35,7 @@ import org.springframework.transaction.support.TransactionTemplate; import java.io.IOException; +import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.junit.Assert.*; @@ -108,9 +109,9 @@ public class Neo4jTemplateApiTransactionTests { new TransactionTemplate(transactionManager).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { - node0 = graphDatabase.createNode(map("name", "node0")); + node0 = graphDatabase.createNode(map("name", "node0"), asList("Node")); graphDatabase.createIndex(Node.class, "node", IndexType.SIMPLE).add(node0, "name", "node0"); - node1 = graphDatabase.createNode(map("name", "node1")); + node1 = graphDatabase.createNode(map("name", "node1"), asList("Node")); relationship1 = node0.createRelationshipTo(node1, KNOWS); relationship1.setProperty("name", "rel1"); graphDatabase.createIndex(Relationship.class, "relationship", IndexType.SIMPLE).add(relationship1, "name", "rel1"); @@ -197,7 +198,7 @@ public class Neo4jTemplateApiTransactionTests { template.exec(new GraphCallback.WithoutResult() { @Override public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception { - graph.createNode(null); + graph.createNode(null, labels); } }); } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTests.java index 15732b292..dfc327f00 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/template/Neo4jTemplateTests.java @@ -56,7 +56,7 @@ public class Neo4jTemplateTests extends NeoApiTests { template.exec(new GraphCallback.WithoutResult() { @Override public void doWithGraphWithoutResult(GraphDatabase graph) throws Exception { - Node node = graph.createNode(map("name", "Test", "size", 100)); + Node node = graph.createNode(map("name", "Test", "size", 100), null); refNode.createRelationshipTo(node, HAS); final Relationship toTestNode = refNode.getSingleRelationship(HAS, Direction.OUTGOING); @@ -102,7 +102,7 @@ public class Neo4jTemplateTests extends NeoApiTests { super.setUp(); refNode = new Neo4jTemplate(graph, transactionManager).exec(new GraphCallback() { public Node doWithGraph(GraphDatabase graph) throws Exception { - return graph.createNode(map()); + return graph.createNode(map(), null); } }); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java index 1004c1748..b70ab9cfa 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/legacy/UniqueLegacyIndexBasedEntityTests.java @@ -70,8 +70,8 @@ public class UniqueLegacyIndexBasedEntityTests extends CommonUniqueEntityTestBas @Override @Test(expected = DataIntegrityViolationException.class) - @Ignore("This method now throws a DataIntegrityViolationException for legacy indexes" + - " - verify if this is correct") +// @Ignore("This method now throws a DataIntegrityViolationException for legacy indexes" + +// " - verify if this is correct") public void shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity() { CommonUniqueNumericIdClub club1 = createUniqueNumericClub(100L); CommonUniqueNumericIdClub club2 = createUniqueNumericClub(100L); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java index 9f4c6c908..2913264ce 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/unique/schemabased/UniqueSchemaBasedEntityTests.java @@ -70,7 +70,7 @@ public class UniqueSchemaBasedEntityTests extends CommonUniqueEntityTestBase { } @Override - @Ignore("This scenario does not currently work") +// @Ignore("This scenario does not currently work") @Test public void updatingToANewValueShouldKeepTheEntityUniqueAndOldValueShouldBeReusableThereafter() { super.updatingToANewValueShouldKeepTheEntityUniqueAndOldValueShouldBeReusableThereafter();