diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/LeadRelationship.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/LeadRelationship.java new file mode 100644 index 000000000..9b888f246 --- /dev/null +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/LeadRelationship.java @@ -0,0 +1,62 @@ +package org.springframework.data.neo4j.aspects; + +import org.springframework.data.neo4j.annotation.*; +import org.springframework.data.neo4j.support.index.IndexType; + +import java.util.Date; +import java.util.UUID; + +/** +* @author mh +* @since 30.08.15 +*/ +@RelationshipEntity(type="LEAD") +public class LeadRelationship { + + @GraphId + private Long id; + + @Indexed(unique = true) + private Long uuid; + + @Indexed(indexType = IndexType.SIMPLE, indexName = "date-index") + private Date createdDate = new Date(); + + @StartNode + private Person person; + @EndNode + private Group group; + + public LeadRelationship() { + } + + public LeadRelationship(Person person, Group group) { + this.person = person; + this.group = group; + this.uuid = Math.abs(UUID.randomUUID().getMostSignificantBits()); + } + + public Long getId() { + return id; + } + + public Long getUuid() { + return uuid; + } + + public Date getCreatedDate() { + return createdDate; + } + + public Person getPerson() { + return person; + } + + public Group getGroup() { + return group; + } + + public void setCreatedDate(Date createdDate) { + this.createdDate = createdDate; + } +} diff --git a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml index b594b19ea..7c300e62a 100644 --- a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml +++ b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml @@ -121,6 +121,7 @@ org.springframework.data.neo4j.aspects.Developer org.springframework.data.neo4j.aspects.Person org.springframework.data.neo4j.aspects.Group + org.springframework.data.neo4j.aspects.LeadRelationship org.springframework.data.neo4j.aspects.SubGroup org.springframework.data.neo4j.aspects.Toyota org.springframework.data.neo4j.aspects.Volvo diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java index 05063f0db..7ba28756a 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPICypherImpl.java @@ -27,23 +27,30 @@ import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.index.impl.lucene.AbstractIndexHits; import org.neo4j.rest.graphdb.converter.RestEntityExtractor; +import org.neo4j.rest.graphdb.converter.RestIndexHitsConverter; import org.neo4j.rest.graphdb.entity.RestEntity; +import org.neo4j.rest.graphdb.entity.RestEntityCache; import org.neo4j.rest.graphdb.entity.RestNode; import org.neo4j.rest.graphdb.entity.RestRelationship; import org.neo4j.rest.graphdb.index.IndexInfo; import org.neo4j.rest.graphdb.index.RestIndex; import org.neo4j.rest.graphdb.index.RestIndexManager; +import org.neo4j.rest.graphdb.index.SimpleIndexHits; import org.neo4j.rest.graphdb.query.*; +import org.neo4j.rest.graphdb.transaction.TransactionFinishListener; import org.neo4j.rest.graphdb.traversal.RestTraversalDescription; import org.neo4j.rest.graphdb.traversal.RestTraverser; import org.neo4j.rest.graphdb.util.QueryResult; import org.neo4j.rest.graphdb.util.QueryResultBuilder; import org.neo4j.rest.graphdb.util.ResultConverter; +import org.springframework.dao.DataIntegrityViolationException; +import javax.ws.rs.core.Response; import java.util.*; import static java.util.Arrays.asList; import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.rest.graphdb.RestAPIIndexImpl.queryPath; import static org.neo4j.rest.graphdb.query.CypherTransaction.ResultType.row; import static org.neo4j.rest.graphdb.query.CypherTransaction.Statement; @@ -64,8 +71,9 @@ public class RestAPICypherImpl implements RestAPI { public static final String GET_REL_TYPES_QUERY = _MATCH_NODE_QUERY + " MATCH (n)-[r]-() RETURN distinct type(r) as relType"; - private RestIndexManager restIndex = new RestIndexManager(this); - private RestIndexManager restIndexOld; + private RestAPIIndex restAPIIndex; + private final RestEntityCache entityCache = new RestEntityCache(this); + private RestEntityExtractor restEntityExtractor = new RestEntityExtractor(this); private String createNodeQuery(Collection labels) { String labelString = toLabelString(labels); @@ -98,7 +106,7 @@ public class RestAPICypherImpl implements RestAPI { protected RestAPICypherImpl(RestAPI restAPI) { this.restAPI = restAPI; - restIndexOld = new RestIndexManager(restAPI); + restAPIIndex = new RestAPIIndexImpl(this); } @Override @@ -117,8 +125,41 @@ public class RestAPICypherImpl implements RestAPI { } @Override - public RestRelationship addToCache(RestRelationship restRelationship) { - return restAPI.addToCache(restRelationship); + public RestNode addToCache(RestNode restNode) { + return entityCache.addToCache(restNode); + } + + @Override + public RestRelationship addToCache(RestRelationship rel) { + return entityCache.addToCache(rel); + } + + @Override + public RestNode getNodeFromCache(long id) { + return entityCache.getNode(id); + } + + @Override + public RestRelationship getRelFromCache(long id) { + return entityCache.getRelationship(id); + } + + @Override + public void removeNodeFromCache(long id) { + entityCache.removeNode(id); + } + @Override + public void removeRelFromCache(long id) { + entityCache.removeRelationship(id); + } + + @Override + public RestNode getNodeById(long id) { + return getNodeById(id, Load.FromServer); + } + @Override + public RestRelationship getRelationshipById(long id) { + return getRelationshipById(id, Load.FromServer); } @Override @@ -145,33 +186,6 @@ public class RestAPICypherImpl implements RestAPI { } } - public RestNode getNodeFromCache(long id) { - return restAPI.getNodeFromCache(id); - } - public RestRelationship getRelFromCache(long id) { - return restAPI.getRelFromCache(id); - } - - @Override - public void removeNodeFromCache(long id) { - restAPI.removeNodeFromCache(id); - } - @Override - public void removeRelFromCache(long id) { - restAPI.removeRelFromCache(id); - } - - @Override - public RestNode getNodeById(long id) { - return getNodeById(id, Load.FromServer); - } - - @Override - public RestRelationship getRelationshipById(long id) { - return getRelationshipById(id, Load.FromServer); - } - - private RestNode toNode(List row) { long id = ((Number) row.get(0)).longValue(); List labels = (List) row.get(1); @@ -216,10 +230,6 @@ public class RestAPICypherImpl implements RestAPI { return addToCache(toNode(result.next())); } - public RestNode addToCache(RestNode restNode) { - return restAPI.addToCache(restNode); - } - @Override public RestRelationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map props) { String statement = MATCH_NODE_QUERY("n") + MATCH_NODE_QUERY("m") + " CREATE (n)-[r:`" + type.name() + "`]->(m) SET r={props} " + _QUERY_RETURN_REL; @@ -315,6 +325,7 @@ public class RestAPICypherImpl implements RestAPI { @Override public void addLabels(RestNode node, Collection labels) { + if (labels==null || labels.isEmpty()) return; String statement = _MATCH_NODE_QUERY + " SET n" + toLabelString(labels) + _QUERY_RETURN_NODE; CypherTransaction.Result result = runQuery(statement, map("id", node.getId())); @@ -336,9 +347,31 @@ public class RestAPICypherImpl implements RestAPI { return txManager; } + @SuppressWarnings("unchecked") + public IndexHits getIndexQuery(Class entityType, String indexName, String key, Object value) { + String indexPath = RestAPIIndexImpl.indexPath(entityType, indexName, key, value); + RequestResult response = getRestRequest().get(indexPath); + if (response.statusIs(Response.Status.OK)) { + return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response); + } else { + return new SimpleIndexHits(Collections.emptyList(), 0, entityType, this); + } + } + + @SuppressWarnings("unchecked") + public IndexHits queryIndexQuery(Class entityType, String indexName, String key, Object value) { + String indexPath = queryPath(entityType, indexName, key, value); + RequestResult response = getRestRequest().get(indexPath); + if (response.statusIs(Response.Status.OK)) { + return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response); + } else { + return new SimpleIndexHits(Collections.emptyList(), 0, entityType, this); + } + } + @Override public IndexHits getIndex(Class entityType, String indexName, String key, Object value) { - if (value instanceof Query) return restAPI.getIndex(entityType, indexName, key, value); + if (value instanceof Query) return getIndexQuery(entityType, indexName, key, value); String index = key == null ? ":`" + indexName + "`({query})" : ":`" + indexName + "`(`" + key + "`={query})"; if (Node.class.isAssignableFrom(entityType)) { String statement = "start n=node" + index + _QUERY_RETURN_NODE; @@ -355,7 +388,7 @@ public class RestAPICypherImpl implements RestAPI { @Override public IndexHits queryIndex(Class entityType, String indexName, String key, Object value) { - if (value instanceof Query) return restAPI.queryIndex(entityType,indexName,key,value); + if (value instanceof Query) return queryIndexQuery(entityType, indexName, key, value); String index = ":`" + indexName + "`({query})"; if (key != null && !key.isEmpty() && !value.toString().contains(":")) value = key + ":"+value; if (Node.class.isAssignableFrom(entityType)) { @@ -395,7 +428,7 @@ public class RestAPICypherImpl implements RestAPI { @Override public RestIndexManager index() { - return restIndex; + return restAPIIndex.index(); } @@ -403,10 +436,10 @@ public class RestAPICypherImpl implements RestAPI { public void deleteEntity(RestEntity entity) { if (entity instanceof Node) { runQuery(_MATCH_NODE_QUERY + " DELETE n", map("id", entity.getId())); - restAPI.removeNodeFromCache(entity.getId()); + removeNodeFromCache(entity.getId()); } else if (entity instanceof Relationship) { runQuery(_MATCH_REL_QUERY + " DELETE r", map("id", entity.getId())); - restAPI.removeRelFromCache(entity.getId()); + removeRelFromCache(entity.getId()); } } @@ -441,13 +474,13 @@ public class RestAPICypherImpl implements RestAPI { // todo handle within cypher tx @Override public RestNode getOrCreateNode(RestIndex index, String key, Object value, final Map properties, Collection labels) { - return restAPI.getOrCreateNode(index, key, value, properties, labels); + return restAPIIndex.getOrCreateNode(index, key, value, properties, labels); } // todo handle within cypher tx @Override public RestRelationship getOrCreateRelationship(RestIndex index, String key, Object value, final RestNode start, final RestNode end, final String type, final Map properties) { - return restAPI.getOrCreateRelationship(index, key, value, start, end, type, properties); + return restAPIIndex.getOrCreateRelationship(index, key, value, start, end, type, properties); } public CypherResult query(String statement, Map params) { @@ -530,9 +563,15 @@ public class RestAPICypherImpl implements RestAPI { } } + private static final String FULLPATH = "fullpath"; + @Override public RestTraverser traverse(RestNode restNode, Map description) { - return restAPI.traverse(restNode, description); + final RequestResult result = getRestRequest().with(restNode.getUri()).post("traverse/" + FULLPATH, description); + if (result.statusOtherThan(Response.Status.OK)) throw new RuntimeException(String.format("Error executing traversal: %d %s",result.getStatus(), description)); + final Object col = result.toEntity(); + if (!(col instanceof Collection)) throw new RuntimeException(String.format("Unexpected traversal result, %s instead of collection", col != null ? col.getClass() : null)); + return new RestTraverser((Collection) col,restNode.getRestApi()); } public RequestResult batch(Collection> batchRequestData) { @@ -551,25 +590,13 @@ public class RestAPICypherImpl implements RestAPI { @Override @SuppressWarnings("unchecked") public void createIndex(String type, String indexName, Map config) { - restAPI.createIndex(type, indexName, config); + restAPIIndex.createIndex(type, indexName, config); } @Override @SuppressWarnings("unchecked") public RestIndex createIndex(Class type, String indexName, Map config) { - resetIndex(type); - if (Node.class.isAssignableFrom(type)) { - return (RestIndex) index().forNodes( indexName, config); - } - if (Relationship.class.isAssignableFrom(type)) { - return (RestIndex) index().forRelationships(indexName, config); - } - throw new IllegalArgumentException("Required Node or Relationship types to create index, got " + type); - } - - @Override - public void resetIndex(Class type) { - restAPI.resetIndex(type); + return restAPIIndex.createIndex(type,indexName,config); } @Override @@ -634,59 +661,92 @@ public class RestAPICypherImpl implements RestAPI { @Override public boolean isAutoIndexingEnabled(Class clazz) { - return restAPI.isAutoIndexingEnabled(clazz); + return restAPIIndex.isAutoIndexingEnabled(clazz); } @Override public void setAutoIndexingEnabled(Class clazz, boolean enabled) { - restAPI.setAutoIndexingEnabled(clazz, enabled); + restAPIIndex.setAutoIndexingEnabled(clazz, enabled); } @Override public Set getAutoIndexedProperties(Class forClass) { - return restAPI.getAutoIndexedProperties(forClass); + return restAPIIndex.getAutoIndexedProperties(forClass); } @Override public void startAutoIndexingProperty(Class forClass, String s) { - restAPI.startAutoIndexingProperty(forClass, s); + restAPIIndex.startAutoIndexingProperty(forClass, s); } @Override public void stopAutoIndexingProperty(Class forClass, String s) { - restAPI.stopAutoIndexingProperty(forClass, s); + restAPIIndex.stopAutoIndexingProperty(forClass, s); } @Override public void delete(RestIndex index) { - restAPI.delete(index); + restAPIIndex.delete(index); } @Override public void removeFromIndex(RestIndex index, T entity, String key, Object value) { - restAPI.removeFromIndex(index, entity, key, value); + restAPIIndex.removeFromIndex(index, entity, key, value); } @Override public void removeFromIndex(RestIndex index, T entity, String key) { - restAPI.removeFromIndex(index, entity, key); + restAPIIndex.removeFromIndex(index, entity, key); } @Override public void removeFromIndex(RestIndex index, T entity) { - restAPI.removeFromIndex(index, entity); + restAPIIndex.removeFromIndex(index, entity); } @Override - public void addToIndex(T entity, RestIndex index, String key, Object value) { - restAPI.addToIndex(entity, index, key, value); + public void addToIndex(final T entity, final RestIndex index, final String key, final Object value) { + if (!getTxManager().isActive()) { + restAPIIndex.addToIndex(entity, index, key, value); + return; + } + getTxManager().getRemoteCypherTransaction().registerListener(new TransactionFinishListener() { + @Override + public void comitted() { + restAPIIndex.addToIndex(entity, index, key, value); + } + + @Override + public void rolledBack() { + } + }); } @Override @SuppressWarnings("unchecked") - public T putIfAbsent(T entity, RestIndex index, String key, Object value) { - return restAPI.putIfAbsent(entity, index, key, value); + public T putIfAbsent(final T entity, final RestIndex index, final String key, final Object value) { + if (!getTxManager().isActive()) { + return restAPIIndex.putIfAbsent(entity, index, key, value); + } + getTxManager().getRemoteCypherTransaction().registerListener(new TransactionFinishListener() { + @Override + public void comitted() { + T result = restAPIIndex.putIfAbsent(entity, index, key, value); + if (result == null || result.equals(entity)) return; + throw new DataIntegrityViolationException("Unique property "+key+" was to be set to duplicate value "+value); + } + + @Override + public void rolledBack() { + } + }); + return entity; + } + + @Override + public IndexInfo indexInfo(final String indexType) { + return restAPIIndex.indexInfo(indexType); } @Override @@ -694,10 +754,6 @@ public class RestAPICypherImpl implements RestAPI { return restAPI.hasToUpdate(lastUpdate); } - @Override - public IndexInfo indexInfo(final String indexType) { - return restAPI.indexInfo(indexType); - } @Override @@ -721,14 +777,37 @@ public class RestAPICypherImpl implements RestAPI { @Override public RestEntityExtractor getEntityExtractor() { - return restAPI.getEntityExtractor(); + return restEntityExtractor; } @Override public RestEntity createRestEntity(Map data) { - return restAPI.createRestEntity(data); + if (data.containsKey("id") && data.containsKey("properties")) { + long id = asLong(data, "id"); + Map props = (Map) data.get("properties"); + if (data.containsKey("type")) { + return RestRelationship.fromCypher(id,(String)data.get("type"),props,asLong(data, "startNode"),asLong(data, "endNode"),this); + } + if (data.containsKey("labels")) { + List labels = (List) data.get("labels"); + return entityCache.addToCache(RestNode.fromCypher(id,labels,props, this)); + } + } + final String uri = (String) data.get("self"); + if (uri == null || uri.isEmpty()) return null; + if (uri.contains("/node/")) { + return entityCache.addToCache(new RestNode(data, this)); + } + if (uri.contains("/relationship/")) { + return new RestRelationship(data, this); + } + return null; } + protected long asLong(Map data, String idKey) { + Object idValue = data.get(idKey); + return idValue instanceof Number ? ((Number)idValue).longValue() : Long.parseLong(idValue.toString()); + } public Iterable getAllNodes() { String statement = "MATCH (n) " + _QUERY_RETURN_NODE; diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java index 5d12ace1c..c099474d1 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIImpl.java @@ -23,20 +23,16 @@ import org.neo4j.graphdb.*; import org.neo4j.graphdb.index.IndexHits; import org.neo4j.helpers.collection.IterableWrapper; import org.neo4j.helpers.collection.MapUtil; -import org.neo4j.index.lucene.ValueContext; -import org.neo4j.rest.graphdb.entity.RestEntityCache; -import org.neo4j.rest.graphdb.query.CypherRestResult; import org.neo4j.rest.graphdb.converter.RelationshipIterableConverter; import org.neo4j.rest.graphdb.converter.RestEntityExtractor; -import org.neo4j.rest.graphdb.converter.RestIndexHitsConverter; import org.neo4j.rest.graphdb.entity.RestEntity; +import org.neo4j.rest.graphdb.entity.RestEntityCache; import org.neo4j.rest.graphdb.entity.RestNode; import org.neo4j.rest.graphdb.entity.RestRelationship; import org.neo4j.rest.graphdb.index.IndexInfo; import org.neo4j.rest.graphdb.index.RestIndex; import org.neo4j.rest.graphdb.index.RestIndexManager; -import org.neo4j.rest.graphdb.index.RetrievedIndexInfo; -import org.neo4j.rest.graphdb.index.SimpleIndexHits; +import org.neo4j.rest.graphdb.query.CypherRestResult; import org.neo4j.rest.graphdb.query.CypherResult; import org.neo4j.rest.graphdb.query.RestQueryResult; import org.neo4j.rest.graphdb.transaction.NullTransaction; @@ -44,19 +40,14 @@ import org.neo4j.rest.graphdb.traversal.RestDirection; import org.neo4j.rest.graphdb.traversal.RestTraversal; import org.neo4j.rest.graphdb.traversal.RestTraversalDescription; import org.neo4j.rest.graphdb.traversal.RestTraverser; -import org.neo4j.rest.graphdb.util.JsonHelper; import org.neo4j.rest.graphdb.util.QueryResult; import org.neo4j.rest.graphdb.util.ResultConverter; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.util.*; import java.util.concurrent.TimeUnit; -import static java.lang.String.format; import static javax.ws.rs.core.Response.Status.CREATED; import static org.neo4j.helpers.collection.MapUtil.map; import static org.neo4j.rest.graphdb.ExecutingRestRequest.encode; @@ -72,15 +63,16 @@ public class RestAPIImpl implements RestAPI { private long entityRefetchTimeInMillis = TimeUnit.SECONDS.toMillis(1000); //TODO move to cache private final RestEntityCache entityCache = new RestEntityCache(this); private RestEntityExtractor restEntityExtractor = new RestEntityExtractor(this); - private RestIndexManager restIndexManager = new RestIndexManager(this); - private Map indexInfos = new HashMap<>(); + private final RestAPIIndexImpl restIndexAPI; public RestAPIImpl(String uri) { this.restRequest = createRestRequest(uri, null, null); + restIndexAPI = new RestAPIIndexImpl(this); } public RestAPIImpl(String uri, String user, String password) { this.restRequest = createRestRequest(uri, user, password); + restIndexAPI = new RestAPIIndexImpl(this); } protected RestRequest createRestRequest(String uri, String user, String password) { @@ -89,7 +81,7 @@ public class RestAPIImpl implements RestAPI { @Override public RestIndexManager index() { - return restIndexManager; + return restIndexAPI.index(); } @Override @@ -191,20 +183,6 @@ public class RestAPIImpl implements RestAPI { return entityCache.addToCache(node); } - @Override - public RestNode getOrCreateNode(RestIndex index, String key, Object value, final Map properties, Collection labels) { - if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null"); - final Map data = map("key", key, "value", value, "properties", properties); - final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data); - if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) { - RestNode node = (RestNode) getEntityExtractor().convertFromRepresentation(result); - addLabels(node, labels); - node.setLabels(labels); - return entityCache.addToCache(node); - } - throw new RuntimeException(String.format("Error retrieving or creating node for key %s and value %s with index %s", key, value, index.getIndexName())); - } - private String toLabelString(String[] labels) { if (labels==null || labels.length == 0) return ""; StringBuilder sb = new StringBuilder(); @@ -215,7 +193,7 @@ public class RestAPIImpl implements RestAPI { } - private RestNode createRestNode(RequestResult result) { + public RestNode createRestNode(RequestResult result) { if (result.statusIs(Status.NOT_FOUND)) { throw new NotFoundException("Node not found"); } @@ -229,6 +207,16 @@ public class RestAPIImpl implements RestAPI { return entityCache.addToCache(node); } + public RestRelationship createRestRelationship(RequestResult requestResult, PropertyContainer element) { + if (requestResult.statusOtherThan(CREATED)) { + final int status = requestResult.getStatus(); + throw new RuntimeException("Error creating relationship " + status+" "+requestResult.getText()); + } + final String location = requestResult.getLocation(); + if (requestResult.isMap()) return new RestRelationship(requestResult.toMap(), this); + return new RestRelationship(location, this); + } + @Override public RestRelationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map props) { // final RestRequest restRequest = ((RestNode) startNode).getRestRequest(); @@ -242,58 +230,6 @@ public class RestAPIImpl implements RestAPI { return createRestRelationship(requestResult, startNode); } - private RestRelationship createRestRelationship(RequestResult requestResult, PropertyContainer element) { - if (requestResult.statusOtherThan(CREATED)) { - final int status = requestResult.getStatus(); - throw new RuntimeException("Error creating relationship " + status+" "+requestResult.getText()); - } - final String location = requestResult.getLocation(); - if (requestResult.isMap()) return new RestRelationship(requestResult.toMap(), this); - return new RestRelationship(location, this); - } - - @Override - @SuppressWarnings("unchecked") - public RestIndex getIndex(String indexName) { - final RestIndexManager index = this.index(); - if (index.existsForNodes(indexName)) return (RestIndex) index.forNodes(indexName); - if (index.existsForRelationships(indexName)) return (RestIndex) index.forRelationships(indexName); - throw new IllegalArgumentException("Index " + indexName + " does not yet exist"); - } - - @Override - @SuppressWarnings("unchecked") - public void createIndex(String type, String indexName, Map config) { - Map data=new HashMap(); - data.put("name",indexName); - data.put("config",config); - restRequest.post("index/" + type, data); - IndexInfo indexInfo = indexInfos.get(type); - if (indexInfo!=null) indexInfo.setExpired(); - } - - public void resetIndex(Class type) { - if (Node.class.isAssignableFrom(type)) { - indexInfo(RestIndexManager.NODE).setExpired(); - } - if (Relationship.class.isAssignableFrom(type)) { - indexInfo(RestIndexManager.RELATIONSHIP).setExpired(); - } - } - - @Override - @SuppressWarnings("unchecked") - public RestIndex createIndex(Class type, String indexName, Map config) { - resetIndex(type); - if (Node.class.isAssignableFrom(type)) { - return (RestIndex) index().forNodes( indexName, config); - } - if (Relationship.class.isAssignableFrom(type)) { - return (RestIndex) index().forRelationships(indexName, config); - } - throw new IllegalArgumentException("Required Node or Relationship types to create index, got " + type); - } - @Override public void close() { ExecutingRestRequest.shutdown(); @@ -360,56 +296,6 @@ public class RestAPIImpl implements RestAPI { return "MERGE (n:`"+labelName+"` {`"+key+"`: {value}}) ON CREATE SET n={props} "+setLabels+ _QUERY_RETURN_NODE; } - @Override - public boolean isAutoIndexingEnabled(Class clazz) { - RequestResult response = getRestRequest().get(buildPathAutoIndexerStatus(clazz)); - if (response.statusIs(Response.Status.OK)) { - return Boolean.parseBoolean(response.getText()); - } else { - throw new IllegalStateException("received " + response); - } - } - - @Override - public void setAutoIndexingEnabled(Class clazz, boolean enabled) { - RequestResult response = getRestRequest().put(buildPathAutoIndexerStatus(clazz), enabled); - if (response.statusOtherThan(Status.NO_CONTENT)) { - throw new IllegalStateException("received " + response); - } - } - - @Override - public Set getAutoIndexedProperties(Class forClass) { - RequestResult response = getRestRequest().get(buildPathAutoIndexerProperties(forClass).toString()); - Collection autoIndexedProperties = (Collection) JsonHelper.readJson(response.getText()); - return new HashSet(autoIndexedProperties); - } - - @Override - public void startAutoIndexingProperty(Class forClass, String s) { - try { - // we need to use a inputstream instead of the string directly. Otherwise "post" implicitly uses - // StreamJsonHelper.writeJsonTo which quotes a given string - InputStream stream = new ByteArrayInputStream(s.getBytes("UTF-8")); - RequestResult response = getRestRequest().post(buildPathAutoIndexerProperties(forClass).toString(), stream); - if (response.statusOtherThan(Status.NO_CONTENT)) { - throw new IllegalStateException("received " + response); - } - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); - } - - } - - @Override - public void stopAutoIndexingProperty(Class forClass, String s) { - RequestResult response = getRestRequest().delete(buildPathAutoIndexerProperties(forClass).append("/").append(s).toString()); - if (response.statusOtherThan(Status.NO_CONTENT)) { - throw new IllegalStateException("received " + response); - } - } - - @Override public void removeLabel(RestNode node, String label) { RequestResult response = getRestRequest().with(node.getUri()).delete("labels/" + encode(label)); @@ -513,23 +399,6 @@ public class RestAPIImpl implements RestAPI { } } - private String buildPathAutoIndexerStatus(Class clazz) { - return buildPathAutoIndexerBase(clazz).append("/status").toString(); - } - - private StringBuilder buildPathAutoIndexerProperties(Class clazz) { - return buildPathAutoIndexerBase(clazz).append("/properties"); - } - - private StringBuilder buildPathAutoIndexerBase(Class clazz) { - return new StringBuilder().append("index/auto/").append(indexTypeName(clazz)); - } - - - public RestRequest getRestRequest() { - return restRequest; - } - @Override public RestTraversalDescription createTraversalDescription() { @@ -564,63 +433,12 @@ public class RestAPIImpl implements RestAPI { } - public String indexPath( Class entityType, String indexName, String key, Object value ) { - String typeName = indexTypeName(entityType); - return "index/" + typeName + "/" + encode(indexName) + (key!=null? "/" + encode(key) :"") + (value!=null ? "/" + encode(value):""); - } - - private String indexTypeName(Class entityType) { - return entityType.getSimpleName().toLowerCase(); - } - - public String indexPath( Class entityType, String indexName ) { - return "index/" + indexTypeName(entityType) + "/" + encode(indexName); - } - - private String queryPath( Class entityType, String indexName, String key, Object value ) { - return indexPath( entityType, indexName, key,null) + "?query="+ encode(value); - } - - @Override - @SuppressWarnings("unchecked") - public IndexHits getIndex(Class entityType, String indexName, String key, Object value) { - String indexPath = indexPath(entityType, indexName, key, value); - RequestResult response = restRequest.get(indexPath); - if (response.statusIs(Response.Status.OK)) { - return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response); - } else { - return new SimpleIndexHits(Collections.emptyList(), 0, entityType, this); - } - } - @Override - @SuppressWarnings("unchecked") - public IndexHits queryIndex(Class entityType, String indexName, String key, Object value) { - String indexPath = queryPath(entityType, indexName, key, value); - RequestResult response = restRequest.get(indexPath); - if (response.statusIs(Response.Status.OK)) { - return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response); - } else { - return new SimpleIndexHits(Collections.emptyList(), 0, entityType, this); - } - } - @Override public void deleteEntity(RestEntity entity) { getRestRequest().with(entity.getUri()).delete( "" ); entityCache.removeNode(entity.getId()); } - @Override - public IndexInfo indexInfo(final String indexType) { - IndexInfo indexInfo = indexInfos.get(indexType); - if (indexInfo != null && !indexInfo.isExpired()) { - return indexInfo; - } - RequestResult response = restRequest.get("index/" + encode(indexType)); - indexInfo = new RetrievedIndexInfo(response); - indexInfos.put(indexType,indexInfo); - return indexInfo; - } - + @Override public void setPropertyOnEntity(RestEntity entity, String key, Object value) { RequestResult result = getRestRequest().with(entity.getUri()).put("properties/" + encode(key), value); @@ -649,81 +467,6 @@ public class RestAPIImpl implements RestAPI { return properties; } - private void deleteIndex(String indexPath) { - getRestRequest().delete(indexPath); - } - - @Override - public void delete(RestIndex index) { - deleteIndex(indexPath(index, null, null)); - resetIndex(index.getEntityType()); - } - - @Override - public void removeFromIndex(RestIndex index, T entity, String key, Object value) { - String indexPath = indexPath(index, key, value); - deleteIndex(indexPath(indexPath, entity)); - resetIndex(index.getEntityType()); - } - - protected String indexPath(String indexPath, T restEntity) { - return indexPath + "/" + ((RestEntity)restEntity).getId(); - } - - @Override - public void removeFromIndex(RestIndex index, T entity, String key) { - String indexPath = indexPath(index, key, null); - deleteIndex(indexPath(indexPath, entity)); - resetIndex(index.getEntityType()); - } - - private String indexPath(RestIndex index, String key, Object value) { - return indexPath(index.getEntityType(), index.getIndexName(), key, value); - } - - @Override - public void removeFromIndex(RestIndex index, T entity) { - deleteIndex(indexPath(indexPath(index, null, null), entity)); - resetIndex(index.getEntityType()); - } - - public String uniqueIndexPath(RestIndex index) { - return indexPath(index,null,null) + "?uniqueness=get_or_create"; - } - - @Override - public void addToIndex(T entity, RestIndex index, String key, Object value) { - final RestEntity restEntity = (RestEntity) entity; - String uri = restEntity.getUri(); - if (value instanceof ValueContext) { - value = ((ValueContext)value).getCorrectValue(); - } - final Map data = map("key", key, "value", value, "uri", uri); - final RequestResult result = getRestRequest().post(indexPath(index, null, null), data); - if (result.statusOtherThan(Status.CREATED)) throw new RuntimeException(String.format("Error adding element %d %s %s to index %s", restEntity.getId(), key, value, index.getIndexName())); - } - - @Override - @SuppressWarnings("unchecked") - public T putIfAbsent(T entity, RestIndex index, String key, Object value) { - final RestEntity restEntity = (RestEntity) entity; - restEntity.flush(); - String uri = restEntity.getUri(); - if (value instanceof ValueContext) { - value = ((ValueContext)value).getCorrectValue(); - } - final Map data = map("key", key, "value", value, "uri", uri); - final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data); - if (result.statusIs(Response.Status.CREATED)) { - if (index.getEntityType().equals(Node.class)) return (T)createRestNode(result); - if (index.getEntityType().equals(Relationship.class)) return (T)createRestRelationship(result,restEntity); - } - if (result.statusIs(Response.Status.OK)) { - return (T) getEntityExtractor().convertFromRepresentation(result); - } - throw new RuntimeException(String.format("Error adding element %d %s %s to index %s", restEntity.getId(), key, value, index.getIndexName())); - } - @Override public boolean hasToUpdate(long lastUpdate) { return timeElapsed(lastUpdate, getEntityRefetchTimeInMillis()); @@ -738,18 +481,6 @@ public class RestAPIImpl implements RestAPI { return System.currentTimeMillis() - since > isItGreaterThanThis; } - @Override - public RestRelationship getOrCreateRelationship(RestIndex index, String key, Object value, final RestNode start, final RestNode end, final String type, final Map properties) { - if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null"); - if (start == null || end == null || type == null) throw new IllegalArgumentException("Neither start, end nore type must be null"); - final Map data = map("key", key, "value", value, "properties", properties, "start", start.getUri(), "end", end.getUri(), "type", type); - final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data); - if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) { - return (RestRelationship) getEntityExtractor().convertFromRepresentation(result); - } - throw new RuntimeException(String.format("Error retrieving or creating relationship for key %s and value %s with index %s", key, value, index.getIndexName())); - } - public org.neo4j.rest.graphdb.query.CypherResult query(String statement, Map params) { params = (params==null) ? Collections.emptyMap() : params; final RequestResult requestResult = getRestRequest().post("cypher", map("query", statement, "params", params)); @@ -822,6 +553,11 @@ public class RestAPIImpl implements RestAPI { return null; } + @Override + public RestRequest getRestRequest() { + return restRequest; + } + protected long asLong(Map data, String idKey) { Object idValue = data.get(idKey); return idValue instanceof Number ? ((Number)idValue).longValue() : Long.parseLong(idValue.toString()); @@ -830,4 +566,100 @@ public class RestAPIImpl implements RestAPI { public RequestResult batch(Collection> batchRequestData) { return restRequest.post("batch",batchRequestData); } + + @Override + public IndexInfo indexInfo(String indexType) { + return restIndexAPI.indexInfo(indexType); + } + + @Override + public void stopAutoIndexingProperty(Class forClass, String s) { + restIndexAPI.stopAutoIndexingProperty(forClass, s); + } + + @Override + public void startAutoIndexingProperty(Class forClass, String s) { + restIndexAPI.startAutoIndexingProperty(forClass, s); + } + + @Override + public Set getAutoIndexedProperties(Class forClass) { + return restIndexAPI.getAutoIndexedProperties(forClass); + } + + @Override + public void setAutoIndexingEnabled(Class clazz, boolean enabled) { + restIndexAPI.setAutoIndexingEnabled(clazz, enabled); + } + + @Override + public boolean isAutoIndexingEnabled(Class clazz) { + return restIndexAPI.isAutoIndexingEnabled(clazz); + } + + @Override + public RestIndex createIndex(Class type, String indexName, Map config) { + return restIndexAPI.createIndex(type, indexName, config); + } + + @Override + public void createIndex(String type, String indexName, Map config) { + restIndexAPI.createIndex(type, indexName, config); + } + + @Override + public RestIndex getIndex(String indexName) { + return restIndexAPI.getIndex(indexName); + } + + @Override + public RestRelationship getOrCreateRelationship(RestIndex index, String key, Object value, RestNode start, RestNode end, String type, Map properties) { + return restIndexAPI.getOrCreateRelationship(index, key, value, start, end, type, properties); + } + + @Override + public RestNode getOrCreateNode(RestIndex index, String key, Object value, Map properties, Collection labels) { + return restIndexAPI.getOrCreateNode(index, key, value, properties, labels); + } + + @Override + public T putIfAbsent(T entity, RestIndex index, String key, Object value) { + return restIndexAPI.putIfAbsent(entity, index, key, value); + } + + @Override + public void addToIndex(T entity, RestIndex index, String key, Object value) { + restIndexAPI.addToIndex(entity, index, key, value); + } + + @Override + public void removeFromIndex(RestIndex index, T entity) { + restIndexAPI.removeFromIndex(index, entity); + } + + @Override + public void removeFromIndex(RestIndex index, T entity, String key) { + restIndexAPI.removeFromIndex(index, entity, key); + } + + @Override + public void removeFromIndex(RestIndex index, T entity, String key, Object value) { + restIndexAPI.removeFromIndex(index, entity, key, value); + } + + @Override + public void delete(RestIndex index) { + restIndexAPI.delete(index); + } + + @Override + public IndexHits queryIndex(Class entityType, String indexName, String key, Object value) { + return restIndexAPI.queryIndex(entityType, indexName, key, value); + } + + @Override + public IndexHits getIndex(Class entityType, String indexName, String key, Object value) { + return restIndexAPI.getIndex(entityType, indexName, key, value); + } + } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIIndexImpl.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIIndexImpl.java new file mode 100644 index 000000000..fc8e5dab6 --- /dev/null +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIIndexImpl.java @@ -0,0 +1,332 @@ +package org.neo4j.rest.graphdb; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.graphdb.PropertyContainer; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.index.IndexHits; +import org.neo4j.index.lucene.ValueContext; +import org.neo4j.rest.graphdb.converter.RestIndexHitsConverter; +import org.neo4j.rest.graphdb.entity.RestEntity; +import org.neo4j.rest.graphdb.entity.RestNode; +import org.neo4j.rest.graphdb.entity.RestRelationship; +import org.neo4j.rest.graphdb.index.*; +import org.neo4j.rest.graphdb.util.JsonHelper; + +import javax.ws.rs.core.Response; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.*; + +import static javax.ws.rs.core.Response.Status.CREATED; +import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.rest.graphdb.ExecutingRestRequest.encode; + +/** + * @author mh + * @since 25.08.15 + */ +public class RestAPIIndexImpl implements RestAPIIndex { + private final RestRequest restRequest; + private RestIndexManager restIndexManager; + private Map indexInfos = new HashMap<>(); + private final RestAPI restAPI; + + public RestAPIIndexImpl(RestAPI restAPI) { + this.restAPI = restAPI; + restRequest = restAPI.getRestRequest(); + restIndexManager = new RestIndexManager(restAPI); + } + + @Override + public RestIndexManager index() { + return restIndexManager; + } + + public static String indexPath( Class entityType, String indexName ) { + return "index/" + indexTypeName(entityType) + "/" + encode(indexName); + } + + public static String queryPath( Class entityType, String indexName, String key, Object value ) { + return indexPath( entityType, indexName, key,null) + "?query="+ encode(value); + } + + public static String indexPath( Class entityType, String indexName, String key, Object value ) { + String typeName = indexTypeName(entityType); + return "index/" + typeName + "/" + encode(indexName) + (key!=null? "/" + encode(key) :"") + (value!=null ? "/" + encode(value):""); + } + + public static String indexTypeName(Class entityType) { + return entityType.getSimpleName().toLowerCase(); + } + + @Override + @SuppressWarnings("unchecked") + public IndexHits getIndex(Class entityType, String indexName, String key, Object value) { + String indexPath = indexPath(entityType, indexName, key, value); + RequestResult response = restRequest.get(indexPath); + if (response.statusIs(Response.Status.OK)) { + return new RestIndexHitsConverter(restAPI, entityType).convertFromRepresentation(response); + } else { + return new SimpleIndexHits(Collections.emptyList(), 0, entityType, restAPI); + } + } + @Override + @SuppressWarnings("unchecked") + public IndexHits queryIndex(Class entityType, String indexName, String key, Object value) { + String indexPath = queryPath(entityType, indexName, key, value); + RequestResult response = restRequest.get(indexPath); + if (response.statusIs(Response.Status.OK)) { + return new RestIndexHitsConverter(restAPI, entityType).convertFromRepresentation(response); + } else { + return new SimpleIndexHits(Collections.emptyList(), 0, entityType, restAPI); + } + } + private void deleteIndex(String indexPath) { + getRestRequest().delete(indexPath); + } + + @Override + public void delete(RestIndex index) { + deleteIndex(indexPath(index, null, null)); + resetIndex(index.getEntityType()); + } + + @Override + public void removeFromIndex(RestIndex index, T entity, String key, Object value) { + String indexPath = indexPath(index, key, value); + deleteIndex(indexPath(indexPath, entity)); + resetIndex(index.getEntityType()); + } + + protected String indexPath(String indexPath, T restEntity) { + return indexPath + "/" + ((RestEntity)restEntity).getId(); + } + + @Override + public void removeFromIndex(RestIndex index, T entity, String key) { + String indexPath = indexPath(index, key, null); + deleteIndex(indexPath(indexPath, entity)); + resetIndex(index.getEntityType()); + } + + private String indexPath(RestIndex index, String key, Object value) { + return indexPath(index.getEntityType(), index.getIndexName(), key, value); + } + + @Override + public void removeFromIndex(RestIndex index, T entity) { + deleteIndex(indexPath(indexPath(index, null, null), entity)); + resetIndex(index.getEntityType()); + } + + public String uniqueIndexPath(RestIndex index) { + return indexPath(index,null,null) + "?uniqueness=get_or_create"; + } + + @Override + public void addToIndex(T entity, RestIndex index, String key, Object value) { + final RestEntity restEntity = (RestEntity) entity; + String uri = restEntity.getUri(); + if (value instanceof ValueContext) { + value = ((ValueContext)value).getCorrectValue(); + } + final Map data = map("key", key, "value", value, "uri", uri); + final RequestResult result = getRestRequest().post(indexPath(index, null, null), data); + if (result.statusOtherThan(Response.Status.CREATED)) { + throw new RuntimeException(String.format("Error adding element %d %s %s to index %s status %s\n%s", restEntity.getId(), key, value, index.getIndexName(), result.getStatus(),result.getText())); + } + } + + @Override + @SuppressWarnings("unchecked") + public T putIfAbsent(T entity, RestIndex index, String key, Object value) { + final RestEntity restEntity = (RestEntity) entity; + restEntity.flush(); + String uri = restEntity.getUri(); + if (value instanceof ValueContext) { + value = ((ValueContext)value).getCorrectValue(); + } + final Map data = map("key", key, "value", value, "uri", uri); + final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data); + if (result.statusIs(Response.Status.CREATED)) { + if (index.getEntityType().equals(Node.class)) return (T)createRestNode(result); + if (index.getEntityType().equals(Relationship.class)) return (T)createRestRelationship(result, restEntity); + } + if (result.statusIs(Response.Status.OK)) { + return (T) restAPI.getEntityExtractor().convertFromRepresentation(result); + } + throw new RuntimeException(String.format("Error adding element %d %s %s to index %s", restEntity.getId(), key, value, index.getIndexName())); + } + + public RestNode createRestNode(RequestResult result) { + if (result.statusIs(Response.Status.NOT_FOUND)) { + throw new NotFoundException("Node not found"); + } + RestNode node = null; + if (result.statusIs(CREATED)) { + node = result.isMap() ? new RestNode(result.toMap(), restAPI) : new RestNode(result.getLocation(), restAPI); + } + if (node == null && result.statusIs(Response.Status.OK)) { + node = new RestNode(result.toMap(), restAPI); + } + return restAPI.addToCache(node); + } + + public RestRelationship createRestRelationship(RequestResult requestResult, PropertyContainer element) { + if (requestResult.statusOtherThan(CREATED)) { + final int status = requestResult.getStatus(); + throw new RuntimeException("Error creating relationship " + status+" "+requestResult.getText()); + } + final String location = requestResult.getLocation(); + if (requestResult.isMap()) return new RestRelationship(requestResult.toMap(), restAPI); + return new RestRelationship(location, restAPI); + } + + @Override + public RestNode getOrCreateNode(RestIndex index, String key, Object value, final Map properties, Collection labels) { + if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null"); + final Map data = map("key", key, "value", value, "properties", properties); + final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data); + if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) { + RestNode node = (RestNode) restAPI.getEntityExtractor().convertFromRepresentation(result); + restAPI.addLabels(node, labels); + node.setLabels(labels); + return restAPI.addToCache(node); + } + throw new RuntimeException(String.format("Error retrieving or creating node for key %s and value %s with index %s", key, value, index.getIndexName())); + } + + @Override + public RestRelationship getOrCreateRelationship(RestIndex index, String key, Object value, final RestNode start, final RestNode end, final String type, final Map properties) { + if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null"); + if (start == null || end == null || type == null) throw new IllegalArgumentException("Neither start, end nore type must be null"); + final Map data = map("key", key, "value", value, "properties", properties, "start", start.getUri(), "end", end.getUri(), "type", type); + final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data); + if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) { + return (RestRelationship) restAPI.getEntityExtractor().convertFromRepresentation(result); + } + throw new RuntimeException(String.format("Error retrieving or creating relationship for key %s and value %s with index %s", key, value, index.getIndexName())); + } + + private String buildPathAutoIndexerStatus(Class clazz) { + return buildPathAutoIndexerBase(clazz).append("/status").toString(); + } + + private StringBuilder buildPathAutoIndexerProperties(Class clazz) { + return buildPathAutoIndexerBase(clazz).append("/properties"); + } + + private StringBuilder buildPathAutoIndexerBase(Class clazz) { + return new StringBuilder().append("index/auto/").append(indexTypeName(clazz)); + } + + + public RestRequest getRestRequest() { + return restRequest; + } + + @Override + @SuppressWarnings("unchecked") + public RestIndex getIndex(String indexName) { + final RestIndexManager index = this.index(); + if (index.existsForNodes(indexName)) return (RestIndex) index.forNodes(indexName); + if (index.existsForRelationships(indexName)) return (RestIndex) index.forRelationships(indexName); + throw new IllegalArgumentException("Index " + indexName + " does not yet exist"); + } + + @Override + @SuppressWarnings("unchecked") + public void createIndex(String type, String indexName, Map config) { + Map data=new HashMap(); + data.put("name",indexName); + data.put("config",config); + restRequest.post("index/" + type, data); + IndexInfo indexInfo = indexInfos.get(type); + if (indexInfo!=null) indexInfo.setExpired(); + } + + public void resetIndex(Class type) { + if (Node.class.isAssignableFrom(type)) { + indexInfo(RestIndexManager.NODE).setExpired(); + } + if (Relationship.class.isAssignableFrom(type)) { + indexInfo(RestIndexManager.RELATIONSHIP).setExpired(); + } + } + + @Override + @SuppressWarnings("unchecked") + public RestIndex createIndex(Class type, String indexName, Map config) { + resetIndex(type); + if (Node.class.isAssignableFrom(type)) { + return (RestIndex) index().forNodes( indexName, config); + } + if (Relationship.class.isAssignableFrom(type)) { + return (RestIndex) index().forRelationships(indexName, config); + } + throw new IllegalArgumentException("Required Node or Relationship types to create index, got " + type); + } + + @Override + public boolean isAutoIndexingEnabled(Class clazz) { + RequestResult response = getRestRequest().get(buildPathAutoIndexerStatus(clazz)); + if (response.statusIs(Response.Status.OK)) { + return Boolean.parseBoolean(response.getText()); + } else { + throw new IllegalStateException("received " + response); + } + } + + @Override + public void setAutoIndexingEnabled(Class clazz, boolean enabled) { + RequestResult response = getRestRequest().put(buildPathAutoIndexerStatus(clazz), enabled); + if (response.statusOtherThan(Response.Status.NO_CONTENT)) { + throw new IllegalStateException("received " + response); + } + } + + @Override + public Set getAutoIndexedProperties(Class forClass) { + RequestResult response = getRestRequest().get(buildPathAutoIndexerProperties(forClass).toString()); + Collection autoIndexedProperties = (Collection) JsonHelper.readJson(response.getText()); + return new HashSet(autoIndexedProperties); + } + + @Override + public void startAutoIndexingProperty(Class forClass, String s) { + try { + // we need to use a inputstream instead of the string directly. Otherwise "post" implicitly uses + // StreamJsonHelper.writeJsonTo which quotes a given string + InputStream stream = new ByteArrayInputStream(s.getBytes("UTF-8")); + RequestResult response = getRestRequest().post(buildPathAutoIndexerProperties(forClass).toString(), stream); + if (response.statusOtherThan(Response.Status.NO_CONTENT)) { + throw new IllegalStateException("received " + response); + } + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException(e); + } + + } + + @Override + public void stopAutoIndexingProperty(Class forClass, String s) { + RequestResult response = getRestRequest().delete(buildPathAutoIndexerProperties(forClass).append("/").append(s).toString()); + if (response.statusOtherThan(Response.Status.NO_CONTENT)) { + throw new IllegalStateException("received " + response); + } + } + + @Override + public IndexInfo indexInfo(final String indexType) { + IndexInfo indexInfo = indexInfos.get(indexType); + if (indexInfo != null && !indexInfo.isExpired()) { + return indexInfo; + } + RequestResult response = restRequest.get("index/" + encode(indexType)); + indexInfo = new RetrievedIndexInfo(response); + indexInfos.put(indexType,indexInfo); + return indexInfo; + } +} diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java index ef09fff90..4685559de 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/RestAPIInternal.java @@ -25,8 +25,6 @@ public interface RestAPIInternal { // todo add to cache or update data in cache RestEntity createRestEntity(Map data); - void resetIndex(Class type); - public enum Load { FromCache, FromServer, diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntity.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntity.java index f2a2601d4..92f19fb9a 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntity.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestEntity.java @@ -69,6 +69,13 @@ public abstract class RestEntity implements PropertyContainer, UpdatableRestResu this.id = id; setProperties((Map) structuralData.get("data")); } + public RestEntity(RestEntity entity, RestAPI facade) { + this.restApi = facade; + this.structuralData = entity.structuralData; + this.id = entity.id; + this.uri = nodeUri(facade,id); + setProperties((Map) structuralData.get("data")); + } public static String nodeUri(RestAPI facade, long id) { return facade.getBaseUri()+"/node/" + id; diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestNode.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestNode.java index 2b466336a..4bc434473 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestNode.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestNode.java @@ -58,6 +58,10 @@ public class RestNode extends RestEntity implements Node { super(id,restData,facade); setLabels(labels); } + public RestNode(RestNode node, RestAPI facade) { + super(node,facade); + setLabels(node.labels); + } public static RestNode fromCypher(long id, Collection labels, Map props, RestAPI facade) { Map restData = map("data", props, "self", RestNode.nodeUri(facade, id));//,"metadata",map("id",String.valueOf("id"),"labels",labels) @@ -73,7 +77,7 @@ public class RestNode extends RestEntity implements Node { public void updateFrom(RestEntity entity, RestAPI restApi) { super.updateFrom(entity, restApi); RestNode node = (RestNode) entity; - if (node.lastLabelFetchTime > 0 && node.labels != null) { + if (node.lastLabelFetchTime > lastLabelFetchTime && node.labels != null) { setLabels(node.labels); } } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java index 7a428a030..bb5754d5c 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/entity/RestRelationship.java @@ -47,6 +47,9 @@ public class RestRelationship extends RestEntity implements Relationship { public RestRelationship( Map data, RestAPI restApi ) { super( data, restApi ); } + public RestRelationship( RestRelationship relationship, RestAPI restApi ) { + super( relationship, restApi ); + } @Override protected void doUpdate() { diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/index/RestIndexManager.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/index/RestIndexManager.java index 48cc0b7b5..b90e6a956 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/index/RestIndexManager.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/index/RestIndexManager.java @@ -28,6 +28,8 @@ import org.neo4j.graphdb.index.*; import org.neo4j.index.impl.lucene.LuceneIndexImplementation; import org.neo4j.rest.graphdb.RestAPI; +import static org.neo4j.rest.graphdb.ExecutingRestRequest.encode; + public class RestIndexManager implements IndexManager { public static final String RELATIONSHIP = "relationship"; public static final String NODE = "node"; diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/transaction/RemoteCypherTransaction.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/transaction/RemoteCypherTransaction.java index 28aaf39b1..d1fea8c1e 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/transaction/RemoteCypherTransaction.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/transaction/RemoteCypherTransaction.java @@ -24,12 +24,17 @@ import org.neo4j.rest.graphdb.query.CypherTransaction; import javax.transaction.Status; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static org.neo4j.helpers.collection.MapUtil.map; public class RemoteCypherTransaction implements Transaction { + private final List listeners = new ArrayList<>(); + int status = Status.STATUS_NO_TRANSACTION; boolean success, failure; CypherTransaction tx; @@ -46,6 +51,12 @@ public class RemoteCypherTransaction implements Transaction { '}'; } + public void registerListener(TransactionFinishListener listener) { + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + public RemoteCypherTransaction(CypherTransaction tx) { this.tx = tx; status = Status.STATUS_ACTIVE; @@ -68,10 +79,6 @@ public class RemoteCypherTransaction implements Transaction { close(); } - public void terminate() { - throw new UnsupportedOperationException(); - } - @Override public void close() { if (tx() != null && innerCounter.decrementAndGet() > 0) { @@ -83,16 +90,25 @@ public class RemoteCypherTransaction implements Transaction { status = Status.STATUS_COMMITTED; } else { - tx().rollback(); + if (tx()!=null) tx().rollback(); status = Status.STATUS_ROLLEDBACK; } } finally { tx = null; + notifyFinish(); } } + private void notifyFinish() { + for (TransactionFinishListener listener : listeners) { + if (status == Status.STATUS_COMMITTED) listener.comitted(); + else listener.rolledBack(); + } + listeners.clear(); + } + private CypherTransaction tx() { - if (tx == null) throw new IllegalStateException("No transaction active"); + if (tx == null && !failure) throw new IllegalStateException("No transaction active"); return tx; } @@ -129,4 +145,9 @@ public class RemoteCypherTransaction implements Transaction { public boolean isActive() { return tx != null; } + + public void terminate() { + failure(); + close(); + } } diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/transaction/TransactionFinishListener.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/transaction/TransactionFinishListener.java new file mode 100644 index 000000000..9c9cc91dc --- /dev/null +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/transaction/TransactionFinishListener.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2002-2013 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.rest.graphdb.transaction; + +/** + * @author mh + * @since 20.05.15 + */ +public interface TransactionFinishListener { + void comitted(); + void rolledBack(); +} diff --git a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/util/QueryResultBuilder.java b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/util/QueryResultBuilder.java index e6061fdc9..9d65c1677 100644 --- a/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/util/QueryResultBuilder.java +++ b/spring-data-neo4j-rest/src/main/java/org/neo4j/rest/graphdb/util/QueryResultBuilder.java @@ -32,7 +32,6 @@ import org.neo4j.rest.graphdb.query.CypherTransaction; public class QueryResultBuilder implements QueryResult { - private CypherTransaction.Result cypherResult; private Iterable result; private final ResultConverter defaultConverter; private final boolean isClosableIterable; @@ -44,18 +43,10 @@ public class QueryResultBuilder implements QueryResult { public QueryResultBuilder(Iterable result, final ResultConverter defaultConverter) { this.result = result; - this.isClosableIterable = result instanceof IndexHits || result instanceof ClosableIterable; + this.isClosableIterable = result instanceof IndexHits || result instanceof ClosableIterable || result instanceof AutoCloseable;; this.defaultConverter = defaultConverter; } - public static String replaceParams(String statement, Map params) { - if (params==null || params.isEmpty()) return statement; - for (Map.Entry param : params.entrySet()) { - statement = statement.replaceAll("%"+param.getKey()+"\\b",""+param.getValue()); - } - return statement; - } - @Override public ConvertedResult to(Class type) { return this.to(type, defaultConverter); @@ -121,9 +112,15 @@ public class QueryResultBuilder implements QueryResult { private void closeIfNeeded() { if (isClosableIterable && !isClosed) { if (result instanceof IndexHits) { - ((IndexHits) result).close(); + ((IndexHits) result).close(); } else if (result instanceof ClosableIterable) { - ((ClosableIterable) result).close(); + ((ClosableIterable) result).close(); + } else if (result instanceof AutoCloseable) { + try { + ((AutoCloseable)result).close(); + } catch (Exception e) { + // ignore + } } isClosed=true; } diff --git a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/MatrixDatabaseTest.java b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/MatrixDatabaseTest.java index 90eedd426..f4ebd1df4 100644 --- a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/MatrixDatabaseTest.java +++ b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/MatrixDatabaseTest.java @@ -32,9 +32,12 @@ import org.neo4j.rest.graphdb.MatrixDataGraph.RelTypes; import org.neo4j.test.ImpermanentGraphDatabase; import org.neo4j.test.TestGraphDatabaseFactory; +import java.util.HashSet; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; - +import static org.neo4j.helpers.collection.IteratorUtil.addToCollection; +import static org.neo4j.helpers.collection.IteratorUtil.asCollection; /** @@ -149,7 +152,7 @@ public class MatrixDatabaseTest { Index goodGuys = index.forNodes("heroes"); IndexHits hits = goodGuys.query( "name", "*" ); Traverser heroesTraverser = getHeroes(); - assertEquals( heroesTraverser.nodes().iterator().next().getId(), hits.iterator().next().getId() ); + assertEquals( addToCollection(heroesTraverser.nodes(), new HashSet()), addToCollection(hits.iterator() , new HashSet())); } diff --git a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestCypherQueryEngineTest.java b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestCypherQueryEngineTest.java index 228199814..9944ba5f7 100644 --- a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestCypherQueryEngineTest.java +++ b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestCypherQueryEngineTest.java @@ -28,6 +28,7 @@ import java.util.Map; import org.junit.Before; import org.junit.Test; +import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.helpers.collection.IteratorUtil; @@ -40,7 +41,11 @@ public class RestCypherQueryEngineTest extends RestTestBase { private RestAPI restAPI; private MatrixDataGraph embeddedMatrixdata; private MatrixDataGraph restMatrixData; - + + protected GraphDatabaseService createRestGraphDatabase() { + return new RestGraphDatabase(SERVER_ROOT_URI); + } + @Before public void init() throws Exception { embeddedMatrixdata = new MatrixDataGraph(getGraphDatabase(),nodeId()).createNodespace(); diff --git a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestIndexTest.java b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestIndexTest.java index e3ef2e231..38b082ee4 100644 --- a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestIndexTest.java +++ b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/RestIndexTest.java @@ -28,6 +28,7 @@ import org.apache.lucene.index.Term; import org.apache.lucene.search.TermQuery; import org.junit.Assert; import org.junit.Test; +import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.index.Index; @@ -43,6 +44,10 @@ public class RestIndexTest extends RestTestBase { private static final String NODE_INDEX_NAME = "NODE_INDEX"; private static final String REL_INDEX_NAME = "REL_INDEX"; + protected GraphDatabaseService createRestGraphDatabase() { + return new RestGraphDatabase(SERVER_ROOT_URI); + } + @Test public void testAddToNodeIndex() { nodeIndex().add(node(), "name", "test"); diff --git a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipRestApiImplTest.java b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipRestApiImplTest.java index 323d120c5..6f89525fc 100644 --- a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipRestApiImplTest.java +++ b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipRestApiImplTest.java @@ -1,6 +1,8 @@ package org.neo4j.rest.graphdb; +import org.junit.Test; import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException; /** * @author mh @@ -13,4 +15,9 @@ public class UpdateRelationshipRestApiImplTest extends UpdateRelationshipTest { restAPI = new RestAPIImpl(SERVER_ROOT_URI); return new RestGraphDatabase(restAPI); } + + @Test + public void testUpdateRelationshipsRemoveAddNoType() throws Exception { + super.testUpdateRelationshipsRemoveAddNoType(); + } } diff --git a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipTest.java b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipTest.java index 2cd66c341..68318db7a 100644 --- a/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipTest.java +++ b/spring-data-neo4j-rest/src/test/java/org/neo4j/rest/graphdb/UpdateRelationshipTest.java @@ -67,6 +67,13 @@ public class UpdateRelationshipTest extends RestTestBase { updateRelationships(); } + /* + given + (remove)-[:LIKES]->(node) + (node)-[:KNOWS]->(keep) + when update to (node)-->(keep) + then + */ @Test(expected = CypherTransactionExecutionException.class) public void testUpdateRelationshipsRemoveAddNoType() throws Exception { Node node = node(); @@ -74,6 +81,7 @@ public class UpdateRelationshipTest extends RestTestBase { node.createRelationshipTo(keep, KNOWS); type = null; updateTo = asList(keep); + expected = asList(remove, keep); updateRelationships(); } diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestEntityPropertyValidationTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestEntityPropertyValidationTests.java index 5f5c99e35..3d3a2e17b 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestEntityPropertyValidationTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestEntityPropertyValidationTests.java @@ -16,19 +16,13 @@ package org.springframework.data.neo4j.rest.integration; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; +import org.junit.*; import org.junit.runner.RunWith; import org.springframework.data.neo4j.aspects.support.EntityPropertyValidationTests; import org.springframework.data.neo4j.rest.support.RestTestBase; import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; /** * @author mh @@ -37,7 +31,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestEntityPropertyValidationTests extends EntityPropertyValidationTests { @BeforeClass diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestFinderTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestFinderTests.java index c1843597e..eb95c3cf4 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestFinderTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestFinderTests.java @@ -19,15 +19,14 @@ package org.springframework.data.neo4j.rest.integration; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.neo4j.aspects.support.FinderTests; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; /** * @author mh @@ -36,7 +35,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestFinderTests extends FinderTests { @BeforeClass @@ -54,4 +52,9 @@ public class RestFinderTests extends FinderTests { RestTestBase.shutdownDb(); } + @Test + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public void testFindRelationshipEntity() { + super.testFindRelationshipEntity(); + } } diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestGraphRepositoryTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestGraphRepositoryTests.java index b9ec18822..2146293c2 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestGraphRepositoryTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestGraphRepositoryTests.java @@ -23,13 +23,9 @@ import org.junit.runner.RunWith; import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException; import org.springframework.data.neo4j.aspects.support.GraphRepositoryTests; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.transaction.BeforeTransaction; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; /** * @author mh @@ -38,7 +34,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestGraphRepositoryTests extends GraphRepositoryTests { @BeforeClass diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestIndexTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestIndexTests.java index a5a9ffcc7..18051b250 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestIndexTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestIndexTests.java @@ -25,12 +25,8 @@ import org.springframework.data.neo4j.aspects.Person; import org.springframework.data.neo4j.aspects.support.IndexTests; import org.springframework.data.neo4j.rest.support.RestTestBase; import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.Transactional; import static org.junit.Assert.assertEquals; @@ -43,7 +39,7 @@ import static org.springframework.data.neo4j.aspects.Person.persistedPerson; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", - "classpath:RestTests-context.xml"}) + "classpath:RestTests-context-index.xml"}) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) public class RestIndexTests extends IndexTests { diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNeo4jTemplateApiTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNeo4jTemplateApiTests.java index 3ce09ee8e..7b1851ee4 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNeo4jTemplateApiTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNeo4jTemplateApiTests.java @@ -24,12 +24,16 @@ import org.junit.Ignore; import org.junit.Test; import org.neo4j.graphdb.GraphDatabaseService; import org.springframework.dao.DataAccessException; +import org.springframework.data.neo4j.config.JtaTransactionManagerFactoryBean; import org.springframework.data.neo4j.config.NullTransactionManager; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.rest.support.RestTestHelper; import org.springframework.data.neo4j.template.Neo4jTemplateApiTests; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.jta.JtaTransactionManager; +import org.springframework.transaction.jta.UserTransactionAdapter; + +import javax.transaction.TransactionManager; public class RestNeo4jTemplateApiTests extends Neo4jTemplateApiTests { @@ -64,7 +68,8 @@ public class RestNeo4jTemplateApiTests extends Neo4jTemplateApiTests @Override protected PlatformTransactionManager createTransactionManager() { - return new JtaTransactionManager(new NullTransactionManager()); + TransactionManager txm = graphDatabase.getTransactionManager(); + return new JtaTransactionManager(new UserTransactionAdapter( txm ), txm); } @Override diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityRelationshipTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityRelationshipTests.java index 0f4765cd2..29b97132d 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityRelationshipTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityRelationshipTests.java @@ -31,12 +31,8 @@ import org.springframework.data.neo4j.aspects.Mentorship; import org.springframework.data.neo4j.aspects.Person; import org.springframework.data.neo4j.aspects.support.NodeEntityRelationshipTests; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.Transactional; import static org.junit.Assert.assertEquals; @@ -49,7 +45,6 @@ import static org.junit.Assert.assertFalse; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestNodeEntityRelationshipTests extends NodeEntityRelationshipTests { @BeforeClass diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityTests.java index 1b0c66876..97ca039b4 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestNodeEntityTests.java @@ -22,12 +22,8 @@ import org.neo4j.graphdb.ConstraintViolationException; import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException; import org.springframework.data.neo4j.aspects.support.NodeEntityTests; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; /** * @author mh @@ -35,8 +31,7 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", - "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) + "classpath:RestTests-context-index.xml"}) public class RestNodeEntityTests extends NodeEntityTests { @BeforeClass @@ -60,7 +55,7 @@ public class RestNodeEntityTests extends NodeEntityTests { // super.testSetShortProperty(); } - @Test(expected = CypherTransactionExecutionException.class) + @Test(expected = IllegalStateException.class) public void testDefaultFailOnDuplicateSetToTrueCausesExceptionWhenAnotherDuplicateEntityCreated() { super.testDefaultFailOnDuplicateSetToTrueCausesExceptionWhenAnotherDuplicateEntityCreated(); } diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestProjectionTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestProjectionTests.java index eaf7f57fc..00eb67586 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestProjectionTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestProjectionTests.java @@ -22,12 +22,8 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.springframework.data.neo4j.aspects.support.ProjectionTests; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; /** * @author mh @@ -36,7 +32,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestProjectionTests extends ProjectionTests { @BeforeClass diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestPropertyTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestPropertyTests.java index 1611ba4bb..05bcaca97 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestPropertyTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestPropertyTests.java @@ -22,12 +22,8 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.springframework.data.neo4j.aspects.support.PropertyTests; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; /** * @author mh @@ -36,7 +32,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestPropertyTests extends PropertyTests { @BeforeClass diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipEntityTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipEntityTests.java index 2584aa258..b4f9bacb1 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipEntityTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipEntityTests.java @@ -16,27 +16,30 @@ package org.springframework.data.neo4j.rest.integration; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; +import org.junit.*; import org.junit.runner.RunWith; +import org.springframework.data.neo4j.aspects.LeadRelationship; import org.springframework.data.neo4j.aspects.support.RelationshipEntityTests; +import org.springframework.data.neo4j.aspects.Group; +import org.springframework.data.neo4j.aspects.Person; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; +import org.springframework.transaction.annotation.Transactional; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import static org.junit.Assert.assertEquals; /** * @author mh * @since 28.03.11 */ + @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestRelationshipEntityTests extends RelationshipEntityTests { @BeforeClass @@ -55,4 +58,21 @@ public class RestRelationshipEntityTests extends RelationshipEntityTests { } + @Test + @Transactional + public void testSaveRelationship() throws Exception { + Person person = personRepository.save(new Person("Michael", 39)); + Group group = groupRepository.save(new Group()); + LeadRelationship rel = new LeadRelationship(person,group); + LeadRelationship saved = neo4jTemplate.save(rel); + LeadRelationship loaded = neo4jTemplate.findOne(saved.getId(),LeadRelationship.class); + assertEquals(saved.getUuid(),loaded.getUuid()); + saved.setCreatedDate(new Date()); + LeadRelationship saved2 = neo4jTemplate.save(saved); + LeadRelationship loaded2 = neo4jTemplate.findOne(saved2.getId(),LeadRelationship.class); + assertEquals(saved.getId(),loaded2.getId()); + SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm"); + assertEquals(format.format(saved2.getCreatedDate()),format.format(loaded2.getCreatedDate())); + } + } diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipTests.java index f4521838f..3af8b6d63 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestRelationshipTests.java @@ -33,12 +33,8 @@ import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; import org.springframework.data.neo4j.rest.SpringRestGraphDatabase; import org.springframework.data.neo4j.rest.support.RestTestBase; import org.springframework.data.neo4j.support.Neo4jTemplate; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.EnableTransactionManagement; import static org.junit.Assert.assertEquals; @@ -49,7 +45,6 @@ import static org.junit.Assert.assertEquals; */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = RestRelationshipTests.MyConfig.class) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestRelationshipTests { @Configuration diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestTraversalTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestTraversalTests.java index 5fa79876d..e5adf542f 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestTraversalTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestTraversalTests.java @@ -22,12 +22,8 @@ import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.springframework.data.neo4j.aspects.support.TraversalTests; import org.springframework.data.neo4j.rest.support.RestTestBase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; /** * @author mh @@ -36,7 +32,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi @RunWith( SpringJUnit4ClassRunner.class ) @ContextConfiguration( locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", "classpath:RestTests-context.xml"} ) -@TestExecutionListeners( {CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class} ) public class RestTraversalTests extends TraversalTests { diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestUniqueEntityTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestUniqueEntityTests.java index 1dcc6a607..2b00de9d7 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestUniqueEntityTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/integration/RestUniqueEntityTests.java @@ -22,12 +22,10 @@ import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.neo4j.rest.support.RestTestBase; import org.springframework.data.neo4j.unique.common.CommonUniqueNumericIdClub; import org.springframework.data.neo4j.unique.legacy.UniqueLegacyIndexBasedEntityTests; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import static org.junit.Assert.assertEquals; @@ -39,7 +37,6 @@ import static org.junit.Assert.assertEquals; @ContextConfiguration(locations = { "classpath:unique-legacy-test-context.xml", "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) public class RestUniqueEntityTests extends UniqueLegacyIndexBasedEntityTests { @BeforeClass @@ -58,9 +55,9 @@ public class RestUniqueEntityTests extends UniqueLegacyIndexBasedEntityTests { } - @Override + @Ignore @Test public void shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity() { - super.shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity(); + // needs transaction separation due to legacy index endpoints not participating in transactions } } diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestEntityTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestEntityTests.java index 41e0b15d3..6df26d1a8 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestEntityTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestEntityTests.java @@ -22,6 +22,7 @@ import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.Relationship; +import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException; import java.util.Arrays; @@ -73,7 +74,7 @@ public class RestEntityTests extends RestTestBase { assertEquals(null, restGraphDatabase.getNodeById(nodeId)); } - @Test(expected = NotFoundException.class) + @Test(expected = RuntimeException.class) public void testRemoveRelationship() { Node refNode = createNode(); Node node = createNode(); diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java index 09371f39a..a4b6befca 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestQueryEngineTests.java @@ -21,18 +21,16 @@ import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.neo4j.rest.graphdb.RestGraphDatabase; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.neo4j.aspects.support.query.QueryEngineTests; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase; -import org.springframework.test.context.CleanContextCacheTestExecutionListener; +import org.springframework.data.neo4j.rest.SpringRestGraphDatabase; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.transaction.BeforeTransaction; -import org.springframework.test.context.transaction.TransactionalTestExecutionListener; /** * @author mh @@ -40,12 +38,11 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml", - "classpath:RestTests-context.xml"}) -@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class}) + "classpath:RestTests-context-index.xml"}) public class RestQueryEngineTests extends QueryEngineTests { @Autowired - SpringCypherRestGraphDatabase restGraphDatabase; + SpringRestGraphDatabase restGraphDatabase; @BeforeClass public static void startDb() throws Exception { diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java index 43a488fca..3e85e3c06 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestBase.java @@ -57,6 +57,7 @@ public class RestTestBase { db = new ImpermanentGraphDatabase(); final ServerConfigurator configurator = new ServerConfigurator(db); configurator.configuration().setProperty(Configurator.WEBSERVER_PORT_PROPERTY_KEY,PORT); + configurator.configuration().setProperty("dbms.security.auth_enabled",false); final WrappingNeoServerBootstrapper bootstrapper = new WrappingNeoServerBootstrapper(db, configurator); int exit = bootstrapper.start(); if (exit != 0 ) throw new IllegalStateException("Server not started correctly."); diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java index ecc6cbbe1..4fa5309bd 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/RestTestHelper.java @@ -41,6 +41,7 @@ public class RestTestHelper db = new ImpermanentGraphDatabase(); final ServerConfigurator configurator = new ServerConfigurator(db); configurator.configuration().setProperty(Configurator.WEBSERVER_PORT_PROPERTY_KEY,PORT); + configurator.configuration().setProperty("dbms.security.auth_enabled",false); final WrappingNeoServerBootstrapper bootstrapper = new WrappingNeoServerBootstrapper(db, configurator); bootstrapper.start(); neoServer = bootstrapper.getServer(); diff --git a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/SpringPluginInitializerTests.java b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/SpringPluginInitializerTests.java index 953df212d..311808a67 100644 --- a/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/SpringPluginInitializerTests.java +++ b/spring-data-neo4j-rest/src/test/java/org/springframework/data/neo4j/rest/support/SpringPluginInitializerTests.java @@ -74,6 +74,7 @@ public class SpringPluginInitializerTests extends SpringPluginInitializer implem }; final Configuration configuration = configurator.configuration(); configuration.setProperty(Configurator.WEBSERVER_PORT_PROPERTY_KEY, PORT); + configuration.setProperty("dbms.security.auth_enabled", false); final WrappingNeoServerBootstrapper bootstrapper = new WrappingNeoServerBootstrapper(db, configurator); touched=0; bootstrapper.start(); diff --git a/spring-data-neo4j-rest/src/test/resources/RestTests-context-index.xml b/spring-data-neo4j-rest/src/test/resources/RestTests-context-index.xml new file mode 100644 index 000000000..4a9acd80b --- /dev/null +++ b/spring-data-neo4j-rest/src/test/resources/RestTests-context-index.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/spring-data-neo4j-rest/src/test/resources/server-test-db.properties b/spring-data-neo4j-rest/src/test/resources/server-test-db.properties index faba9ee75..909653ff0 100644 --- a/spring-data-neo4j-rest/src/test/resources/server-test-db.properties +++ b/spring-data-neo4j-rest/src/test/resources/server-test-db.properties @@ -1,2 +1,4 @@ org.neo4j.server.database.location=target/test-db org.neo4j.server.thirdparty_jaxrs_classes=org.springframework.data.neo4j.rest.support=/test +dbms.security.auth_enabled=false + diff --git a/spring-data-neo4j-rest/src/test/resources/test-db.properties b/spring-data-neo4j-rest/src/test/resources/test-db.properties index 64291a371..f8cd64f32 100644 --- a/spring-data-neo4j-rest/src/test/resources/test-db.properties +++ b/spring-data-neo4j-rest/src/test/resources/test-db.properties @@ -1 +1,2 @@ org.neo4j.server.database.location=target/test-db +dbms.security.auth_enabled=false diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java index 966e77e58..57a328542 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/conversion/QueryResultBuilder.java @@ -25,6 +25,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.neo4j.mapping.MappingPolicy; +import java.io.Closeable; import java.util.*; /** @@ -124,6 +125,12 @@ public class QueryResultBuilder implements Result { ((IndexHits) result).close(); } else if (result instanceof ClosableIterable) { ((ClosableIterable) result).close(); + } else if (result instanceof AutoCloseable) { + try { + ((AutoCloseable)result).close(); + } catch (Exception e) { + // ignore + } } isClosed=true; } 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 66fb53079..63eda10b0 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 @@ -147,6 +147,8 @@ public class Neo4jTemplateApiTests { @Test public void testIndexNode() throws Exception { template.index("node", node1, "name","node1"); + transaction.success();transaction.close(); + transaction = graphDatabase.beginTx(); Index index = graphDatabase.getIndex("node"); Node lookedUpNode= index.get( "name", "node1" ).getSingle(); assertThat("same node from index", lookedUpNode, is(node1)); 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 1e76b6227..d177a0d2f 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 @@ -38,6 +38,7 @@ import org.springframework.data.neo4j.unique.legacy.repository.UniqueClubReposit import org.springframework.data.neo4j.unique.legacy.repository.UniqueNumericIdClubRepository; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/asciidoc/reference/setup.adoc b/src/main/asciidoc/reference/setup.adoc index 9e5a551c0..eb86b0a21 100644 --- a/src/main/asciidoc/reference/setup.adoc +++ b/src/main/asciidoc/reference/setup.adoc @@ -178,7 +178,7 @@ Users of Spring Data Neo4j have two ways of very concisely configuring it. Eithe === XML namespace -The XML namespace can be used to configure Spring Data Neo4j. The `config` element provides an XML-based configuration of Spring Data Neo4j in one line. It has four attributes. * `base-package` points to a set of packages (provided as a comma separated String of names) which SDN will scan for locate all of your domain entity classes (`@NodeEntity` and `@RelationshipEntity`). NOTE: Neo4j 2.0 introduced the requirement to separately manage schema and data transactions which altered some options for SDN with regards be being able to automatically detect and register `@NodeEntity` and `@RelationshipEntity`s on the fly. Several approaches were attempted to try and handle this automatically with SDN 3.0.X, none of which worked in a satisfactory manner. This has resulted in the base-package becoming a mandatory field now with entity metadata handling becoming an explicit step in the lifecycle. +The XML namespace can be used to configure Spring Data Neo4j. The `config` element provides an XML-based configuration of Spring Data Neo4j in one line. It has four attributes. * `base-package` points to a set of packages (provided as a comma separated String of names) which SDN will scan for locate all of your domain entity classes (`@NodeEntity` and `@RelationshipEntity`). NOTE: Neo4j 2.0 introduced the requirement to separately manage schema and data transactions which altered some options for SDN with regards be being able to automatically detect and register `@NodeEntity` and `@RelationshipEntity` on the fly. Several approaches were attempted to try and handle this automatically with SDN 3.0.X, none of which worked in a satisfactory manner. This has resulted in the base-package becoming a mandatory field now with entity metadata handling becoming an explicit step in the lifecycle. * `graphDatabaseService` points out the Neo4j instance to use. * `storeDirectory` is a convenient alternative (instead of `graphDatabaseService`) to point to a directory where a new `EmbeddedGraphDatabase` will be created. * `entityManagerFactory` is only required for cross-store configuration.