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()
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -32,7 +32,7 @@
|
||||
<properties>
|
||||
<project.type>multi</project.type>
|
||||
<dist.id>spring-data-neo4j</dist.id>
|
||||
<springdata.commons>1.8.0.BUILD-SNAPSHOT</springdata.commons>
|
||||
<springdata.commons>1.7.0.RELEASE</springdata.commons>
|
||||
|
||||
<!-- Neo4j 2.0 now requires JDK 7 as a min -->
|
||||
<source.level>1.7</source.level>
|
||||
|
||||
@@ -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<String, Object> props) {
|
||||
return super.getRestAPI().createNode(props);
|
||||
public Node createNode(Map<String, Object> props, Collection<String> 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<String> 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<String, Object> nodeProperties, Collection<String> 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<String, Object> properties) {
|
||||
|
||||
@@ -242,7 +242,8 @@ public abstract class Neo4jConfiguration {
|
||||
public EntityIndexCreator entityIndexCreator() throws Exception {
|
||||
return new EntityIndexCreator(
|
||||
indexProvider(),
|
||||
schemaIndexProvider()
|
||||
schemaIndexProvider(),
|
||||
nodeTypeRepresentationStrategy().isLabelBased()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,13 @@ public interface GraphDatabase {
|
||||
/**
|
||||
* creates the node and initializes its properties
|
||||
*/
|
||||
Node createNode(Map<String, Object> props);
|
||||
Node createNode(Map<String, Object> props, Collection<String> 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<String, Object> properties, Collection<String> labels);
|
||||
/**
|
||||
* creates the node uniquely or returns an existing node with the same index-key-value combination.
|
||||
* properties are used to initialize the node.
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<S extends PropertyContainer, T> 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<T extends PropertyContainer> 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<T> extends PersistentEntity<T, Neo4jPersi
|
||||
Neo4jPersistentProperty getUniqueProperty();
|
||||
|
||||
boolean isUnique();
|
||||
|
||||
Collection<String> getAllLabels();
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> 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<String,Object> params = new HashMap<String,Object>();
|
||||
|
||||
@@ -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<Object> 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<String, Object> props) {
|
||||
return setProperties(delegate.createNode(), props);
|
||||
public Node createNode(Map<String, Object> props, Collection<String> labels) {
|
||||
return setProperties(delegate.createNode(toLabels(labels)), props);
|
||||
}
|
||||
|
||||
private Label[] toLabels(Collection<String> 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 extends PropertyContainer> T setProperties(T primitive, Map<String, Object> properties) {
|
||||
@@ -309,6 +321,10 @@ public class DelegatingGraphDatabase implements GraphDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public Node merge(String labelName, String key, Object value, final Map<String, Object> nodeProperties, Collection<String> labels) {
|
||||
return schemaIndexProvider.merge(labelName,key,value,nodeProperties,labels);
|
||||
}
|
||||
|
||||
public Node getOrCreateNode(String indexName, String key, Object value, final Map<String,Object> 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);
|
||||
|
||||
@@ -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<String, Object> properties) {
|
||||
return getGraphDatabase().createNode(properties);
|
||||
return createNode(properties, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* properties are used to initialize the node.
|
||||
*/
|
||||
@Override
|
||||
public Node createNode(final Map<String, Object> properties,Collection<String> 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<String, Object> properties, Collection<String> labels) {
|
||||
return getGraphDatabase().merge(label, key, value, properties, labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T createNodeAs(Class<T> target, Map<String, Object> 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.<String, Object>emptyMap());
|
||||
if (indexInfo.isLabelBased()) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<Neo4jPersistentProperty>() {
|
||||
@Override
|
||||
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
|
||||
|
||||
@@ -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 extends PropertyContainer> 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.<String,Object>emptyMap());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -56,10 +56,10 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
|
||||
protected <T> Neo4jPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
|
||||
final Class<T> type = typeInformation.getType();
|
||||
if (type.isAnnotationPresent(NodeEntity.class)) {
|
||||
return new Neo4jPersistentEntityImpl<T>(typeInformation);
|
||||
return new Neo4jPersistentEntityImpl<T>(typeInformation,entityAlias);
|
||||
}
|
||||
if (type.isAnnotationPresent(RelationshipEntity.class)) {
|
||||
return new Neo4jPersistentEntityImpl<T>(typeInformation);
|
||||
return new Neo4jPersistentEntityImpl<T>(typeInformation,entityAlias);
|
||||
}
|
||||
throw new InvalidEntityTypeException("Type " + type + " is neither a @NodeEntity nor a @RelationshipEntity");
|
||||
}
|
||||
@@ -73,16 +73,21 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
|
||||
}
|
||||
|
||||
private void updateStoredEntityType(Neo4jPersistentEntityImpl<?> entity, Collection<Neo4jPersistentEntity<?>> superTypeEntities) {
|
||||
entity.updateStoredType(new StoredEntityType(entity, superTypeEntities, entityAlias));
|
||||
entity.updateStoredType(superTypeEntities);
|
||||
if (entityIndexCreator!=null) entityIndexCreator.ensureEntityIndexes(entity);
|
||||
}
|
||||
|
||||
private List<Neo4jPersistentEntity<?>> addSuperTypes(Neo4jPersistentEntity<?> entity) {
|
||||
List<Neo4jPersistentEntity<?>> entities=new ArrayList<Neo4jPersistentEntity<?>>();
|
||||
final Class<?> type = entity.getType();
|
||||
entities.addAll(addPersistentEntityWithCheck(type.getSuperclass()));
|
||||
for (Class<?> anInterface : type.getInterfaces()) {
|
||||
entities.addAll(addPersistentEntityWithCheck(anInterface));
|
||||
List<Neo4jPersistentEntity<?>> entities=new ArrayList<>();
|
||||
Class<?> type = entity.getType();
|
||||
Collection<Class> 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;
|
||||
}
|
||||
|
||||
@@ -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<T> extends BasicPersistentEntity<T, Neo4j
|
||||
private StoredEntityType storedType;
|
||||
private Neo4jPersistentProperty uniqueProperty;
|
||||
private final boolean shouldUseShortNames;
|
||||
private final EntityAlias entityAlias;
|
||||
private Set<String> labels;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Neo4jPersistentEntityImpl} instance.
|
||||
*
|
||||
*
|
||||
* @param information must not be {@literal null}.
|
||||
* @param entityAlias
|
||||
*/
|
||||
public Neo4jPersistentEntityImpl(TypeInformation<T> information) {
|
||||
public Neo4jPersistentEntityImpl(TypeInformation<T> 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<Neo4jPersistentEntity<?>> superTypeEntities) {
|
||||
this.storedType = new StoredEntityType(this,superTypeEntities,entityAlias);
|
||||
this.labels = computeLabels();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -234,4 +241,36 @@ public class Neo4jPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4j
|
||||
public Neo4jPersistentProperty getUniqueProperty() {
|
||||
return uniqueProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getAllLabels() {
|
||||
return labels;
|
||||
}
|
||||
|
||||
private Set<String> computeLabels() {
|
||||
String alias = storedType.getAlias().toString();
|
||||
final Set<String> labels = collectSuperTypeLabels(storedType, new LinkedHashSet<String>());
|
||||
labels.add(alias);
|
||||
doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
|
||||
@Override
|
||||
public void doWithPersistentProperty(Neo4jPersistentProperty persistentProperty) {
|
||||
if (persistentProperty.isIndexed()) {
|
||||
IndexInfo indexInfo = persistentProperty.getIndexInfo();
|
||||
if (indexInfo.isLabelBased()) {
|
||||
labels.add(indexInfo.getIndexName());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return labels;
|
||||
}
|
||||
|
||||
private Set<String> collectSuperTypeLabels(StoredEntityType type, Set<String> labels) {
|
||||
if (type==null) return labels;
|
||||
for (StoredEntityType superType : type.getSuperTypes()) {
|
||||
labels.add(superType.getAlias().toString());
|
||||
collectSuperTypeLabels(superType, labels);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<StoredEntityType> collectSuperTypes(Collection<Neo4jPersistentEntity<?>> superTypeEntities) {
|
||||
Collection<StoredEntityType> result=new ArrayList<StoredEntityType>(superTypeEntities.size());
|
||||
if (superTypeEntities==null) return Collections.emptyList();
|
||||
Collection<StoredEntityType> result=new ArrayList<>(superTypeEntities.size());
|
||||
for (Neo4jPersistentEntity<?> superTypeEntity : superTypeEntities) {
|
||||
result.add(superTypeEntity.getEntityType());
|
||||
}
|
||||
|
||||
@@ -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<String, Object> nodeProperties, Collection<String> 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<String, Object> 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<String> 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";
|
||||
}
|
||||
|
||||
@@ -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<String> labels = collectSuperTypeLabels(type, new HashSet<String>());
|
||||
Set<String> labels = collectSuperTypeLabels(type, new LinkedHashSet<String>());
|
||||
labels.add(alias);
|
||||
labels.add(LABELSTRATEGY_PREFIX + alias);
|
||||
cypherHelper.setLabelsOnNode(state.getId(), labels);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<String, Object> properties);
|
||||
|
||||
/**
|
||||
* Creates a node with the given properties and labels
|
||||
*/
|
||||
Node createNode(final Map<String, Object> properties,Collection<String> labels);
|
||||
|
||||
Node createNode();
|
||||
|
||||
/**
|
||||
@@ -78,6 +84,12 @@ public interface Neo4jOperations {
|
||||
*/
|
||||
Node getOrCreateNode(String index, String key, Object value, Map<String, Object> 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<String, Object> properties, Collection<String> labels);
|
||||
|
||||
/**
|
||||
* Creates a node mapped by the given entity class
|
||||
* @param target mapped entity class or Node.class
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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<Node>() {
|
||||
public Node doWithGraph(GraphDatabase graph) throws Exception {
|
||||
return graph.createNode(map());
|
||||
return graph.createNode(map(), null);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user