DATAGRAPH-483 REST Transaction support
This commit is contained in:
@@ -15,11 +15,13 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.cross_store.config;
|
||||
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.data.neo4j.aspects.config.Neo4jAspectConfiguration;
|
||||
import org.springframework.data.neo4j.config.JtaTransactionManagerFactoryBean;
|
||||
import org.springframework.data.neo4j.cross_store.support.node.CrossStoreNodeDelegatingFieldAccessorFactory;
|
||||
@@ -69,8 +71,8 @@ public class CrossStoreNeo4jConfiguration extends Neo4jAspectConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager neo4jTransactionManager() throws Exception {
|
||||
JtaTransactionManager jtaTm = new JtaTransactionManagerFactoryBean( getGraphDatabaseService() ).getObject();
|
||||
public PlatformTransactionManager neo4jTransactionManager(GraphDatabaseService graphDatabaseService) {
|
||||
JtaTransactionManager jtaTm = new JtaTransactionManagerFactoryBean( graphDatabaseService ).getObject();
|
||||
|
||||
if (isUsingCrossStorePersistence()) {
|
||||
JpaTransactionManager jpaTm = new JpaTransactionManager(getEntityManagerFactory());
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.neo4j.rest.graphdb;
|
||||
|
||||
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.neo4j.graphdb.schema.Schema;
|
||||
import org.neo4j.graphdb.traversal.BidirectionalTraversalDescription;
|
||||
import org.neo4j.kernel.impl.nioneo.store.StoreId;
|
||||
import org.neo4j.rest.graphdb.entity.RestNode;
|
||||
import org.neo4j.rest.graphdb.index.RestIndexManager;
|
||||
import org.neo4j.rest.graphdb.query.RestCypherTransactionManager;
|
||||
import org.neo4j.rest.graphdb.traversal.RestTraversalDescription;
|
||||
import org.neo4j.rest.graphdb.util.ResourceIterableWrapper;
|
||||
|
||||
import javax.transaction.SystemException;
|
||||
import javax.transaction.TransactionManager;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class CypherRestGraphDatabase extends AbstractRemoteDatabase {
|
||||
private RestAPICypherImpl restAPI;
|
||||
|
||||
public CypherRestGraphDatabase(RestAPI restAPI){
|
||||
this.restAPI = new RestAPICypherImpl(restAPI);
|
||||
}
|
||||
|
||||
public CypherRestGraphDatabase(String uri) {
|
||||
this( new RestAPIImpl( uri ));
|
||||
}
|
||||
|
||||
public CypherRestGraphDatabase(String uri, String user, String password) {
|
||||
this(new RestAPIImpl( uri, user, password ));
|
||||
}
|
||||
|
||||
public RestAPI getRestAPI(){
|
||||
return this.restAPI;
|
||||
}
|
||||
|
||||
public RestIndexManager index() {
|
||||
return this.restAPI.index();
|
||||
}
|
||||
|
||||
public Node createNode() {
|
||||
return this.restAPI.createNode(null);
|
||||
}
|
||||
|
||||
public Node getNodeById( long id ) {
|
||||
return this.restAPI.getNodeById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Node> getAllNodes() {
|
||||
return restAPI.getAllNodes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<RelationshipType> getRelationshipTypes() {
|
||||
return this.restAPI.getRelationshipTypes();
|
||||
}
|
||||
|
||||
public Relationship getRelationshipById( long id ) {
|
||||
return this.restAPI.getRelationshipById(id);
|
||||
}
|
||||
@Override
|
||||
public String getStoreDir() {
|
||||
return restAPI.getBaseUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreId storeId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(long timeout) {
|
||||
return restAPI!=null;
|
||||
}
|
||||
|
||||
public RestCypherTransactionManager getTxManager() {
|
||||
return restAPI.getTxManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DependencyResolver getDependencyResolver() {
|
||||
return new DependencyResolver.Adapter() {
|
||||
@Override
|
||||
public <T> T resolveDependency(Class<T> type, SelectionStrategy selector) throws IllegalArgumentException {
|
||||
if (TransactionManager.class.isAssignableFrom(type)) return (T)getTxManager();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction beginTx() {
|
||||
return restAPI.beginTx();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
try {
|
||||
getTxManager().rollback();
|
||||
} catch (SystemException e) {
|
||||
// ignore
|
||||
}
|
||||
restAPI.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createNode(Label... labels) {
|
||||
return restAPI.createNode(null, toLabelNames(labels));
|
||||
}
|
||||
|
||||
public Set<String> toLabelNames(Label[] labels) {
|
||||
Set<String> labelNames = new LinkedHashSet<>(labels.length);
|
||||
for (Label label : labels) labelNames.add(label.name());
|
||||
return labelNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceIterable<Node> findNodesByLabelAndProperty(Label label, String property, Object value) {
|
||||
Iterable<RestNode> nodes = restAPI.getNodesByLabelAndProperty(label.name(), property, value);
|
||||
return new ResourceIterableWrapper<Node,RestNode>(nodes) {
|
||||
protected Node underlyingObjectToObject(RestNode node) {
|
||||
return node;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Schema schema() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestTraversalDescription traversalDescription() {
|
||||
return restAPI.createTraversalDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BidirectionalTraversalDescription bidirectionalTraversalDescription() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Collection<String> getAllLabelNames() {
|
||||
return restAPI.getAllLabelNames();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.neo4j.rest.graphdb.query.CypherRestResult;
|
||||
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.traversal.RestTraversalDescription;
|
||||
import org.neo4j.rest.graphdb.traversal.RestTraverser;
|
||||
import org.neo4j.rest.graphdb.util.QueryResult;
|
||||
import org.neo4j.rest.graphdb.util.ResultConverter;
|
||||
@@ -71,7 +72,7 @@ public interface RestAPI extends RestAPIIndex, RestAPIInternal {
|
||||
|
||||
Iterable<RelationshipType> getRelationshipTypes();
|
||||
|
||||
TraversalDescription createTraversalDescription();
|
||||
RestTraversalDescription createTraversalDescription();
|
||||
|
||||
Iterable<Relationship> getRelationships(RestNode restNode, Direction direction, RelationshipType... types);
|
||||
|
||||
@@ -87,6 +88,7 @@ public interface RestAPI extends RestAPIIndex, RestAPIInternal {
|
||||
|
||||
RestNode addToCache(RestNode restNode);
|
||||
RestNode getFromCache(long id);
|
||||
void removeFromCache(long id);
|
||||
|
||||
void close();
|
||||
}
|
||||
|
||||
@@ -33,13 +33,12 @@ 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.query.CypherResult;
|
||||
import org.neo4j.rest.graphdb.query.CypherTransaction;
|
||||
import org.neo4j.rest.graphdb.query.CypherTxResult;
|
||||
import org.neo4j.rest.graphdb.query.RestQueryResult;
|
||||
import org.neo4j.rest.graphdb.query.*;
|
||||
import org.neo4j.rest.graphdb.transaction.RemoteCypherTransaction;
|
||||
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 javax.ws.rs.core.Response.Status;
|
||||
@@ -52,7 +51,11 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
public static final String _QUERY_RETURN_NODE = " RETURN id(n) as id, labels(n) as labels, n as data";
|
||||
public static final String _QUERY_RETURN_REL = " RETURN id(r) as id, type(r) as type, r as data, id(startNode(r)) as start, id(endNode(r)) as end";
|
||||
public static String MATCH_NODE_QUERY(String name) { return " MATCH ("+name+") WHERE id("+name+") = {id_"+name+"} "; }
|
||||
|
||||
public static String MATCH_NODE_QUERY(String name) {
|
||||
return " MATCH (" + name + ") WHERE id(" + name + ") = {id_" + name + "} ";
|
||||
}
|
||||
|
||||
public static final String _MATCH_NODE_QUERY = " MATCH (n) WHERE id(n) = {id} ";
|
||||
public static final String GET_NODE_QUERY = _MATCH_NODE_QUERY + _QUERY_RETURN_NODE;
|
||||
public static final String _MATCH_REL_QUERY = " START r=rel({id}) ";
|
||||
@@ -67,17 +70,17 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
private String mergeQuery(String labelName, String key, Collection<String> labels) {
|
||||
StringBuilder setLabels = new StringBuilder();
|
||||
if (labels!=null) {
|
||||
if (labels != null) {
|
||||
for (String label : labels) {
|
||||
if (label.equals(labelName)) continue;
|
||||
setLabels.append("SET n:").append(label).append(" ");
|
||||
}
|
||||
}
|
||||
return "MERGE (n:`"+labelName+"` {`"+key+"`: {value}}) ON CREATE SET n={props} "+setLabels+ _QUERY_RETURN_NODE;
|
||||
return "MERGE (n:`" + labelName + "` {`" + key + "`: {value}}) ON CREATE SET n={props} " + setLabels + _QUERY_RETURN_NODE;
|
||||
}
|
||||
|
||||
private String toLabelString(Collection<String> labels) {
|
||||
if (labels==null || labels.size() == 0) return "";
|
||||
if (labels == null || labels.size() == 0) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String label : labels) {
|
||||
sb.append(":").append(label);
|
||||
@@ -86,13 +89,11 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
}
|
||||
|
||||
private final RestAPI restAPI;
|
||||
private RestRequest restRequest;
|
||||
|
||||
private static final ThreadLocal<CypherTransaction> cypherTransaction = new ThreadLocal<>();
|
||||
private final RestCypherTransactionManager txManager = new RestCypherTransactionManager(this);
|
||||
|
||||
protected RestAPICypherImpl(RestAPI restAPI) {
|
||||
this.restAPI = restAPI;
|
||||
this.restRequest = restAPI.getRestRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,7 +102,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
RestNode restNode = getFromCache(id);
|
||||
if (restNode != null) return restNode;
|
||||
}
|
||||
if (force == Load.FromCache) return new RestNode(RestNode.nodeUri(this, id),this);
|
||||
if (force == Load.FromCache) return new RestNode(RestNode.nodeUri(this, id), this);
|
||||
Iterator<List<Object>> result = query(GET_NODE_QUERY, map("id", id)).getData().iterator();
|
||||
if (!result.hasNext()) {
|
||||
throw new NotFoundException("Node not found " + id);
|
||||
@@ -114,6 +115,11 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
return restAPI.getFromCache(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFromCache(long id) {
|
||||
restAPI.removeFromCache(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestNode getNodeById(long id) {
|
||||
return getNodeById(id, Load.FromServer);
|
||||
@@ -122,34 +128,44 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
private RestNode toNode(List<Object> row) {
|
||||
long id = ((Number) row.get(0)).longValue();
|
||||
List<String> labels = (List<String>) row.get(1);
|
||||
Map<String,Object> props = (Map<String, Object>) row.get(2);
|
||||
Map<String, Object> props = (Map<String, Object>) row.get(2);
|
||||
return RestNode.fromCypher(id, labels, props, this);
|
||||
}
|
||||
|
||||
private RestRelationship toRel(List<Object> row) {
|
||||
long id = ((Number) row.get(0)).longValue();
|
||||
String type = (String)row.get(1);
|
||||
Map<String,Object> props = (Map<String, Object>) row.get(2);
|
||||
String type = (String) row.get(1);
|
||||
Map<String, Object> props = (Map<String, Object>) row.get(2);
|
||||
long start = ((Number) row.get(3)).longValue();
|
||||
long end = ((Number) row.get(4)).longValue();
|
||||
return RestRelationship.fromCypher(id, type, props, start,end,this);
|
||||
return RestRelationship.fromCypher(id, type, props, start, end, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestRelationship getRelationshipById(long id) {
|
||||
Iterator<List<Object>> result = query(GET_REL_QUERY, map("id", id)).getData().iterator();
|
||||
if (!result.hasNext()) {
|
||||
throw new NotFoundException("Relationship not found " + id);
|
||||
try {
|
||||
Iterator<List<Object>> result = query(GET_REL_QUERY, map("id", id)).getData().iterator();
|
||||
if (!result.hasNext()) {
|
||||
throw new NotFoundException("Relationship not found " + id);
|
||||
}
|
||||
List<Object> row = result.next();
|
||||
return toRel(row);
|
||||
} catch (NotFoundException e) {
|
||||
throw e;
|
||||
} catch (CypherTransactionExecutionException ctee) {
|
||||
if (ctee.contains("Neo.DatabaseError.Statement.ExecutionFailure","not found")) {
|
||||
throw new NotFoundException("Relationship not found " + id);
|
||||
}
|
||||
throw ctee;
|
||||
}
|
||||
List<Object> row = result.next();
|
||||
return toRel(row);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RestNode createNode(Map<String, Object> props) {
|
||||
return createNode(props,Collections.<String>emptyList());
|
||||
return createNode(props, Collections.<String>emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestNode createNode(Map<String, Object> props, Collection<String> labels) {
|
||||
Map<?, Object> data = props == null ? Collections.emptyMap() : props;
|
||||
@@ -162,12 +178,13 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public RestNode merge(String labelName, String key, Object value, final Map<String, Object> nodeProperties, Collection<String> labels) {
|
||||
if (labelName ==null || key == null || value==null) throw new IllegalArgumentException("Label "+ labelName +" key "+key+" and value must not be null");
|
||||
if (labelName == null || key == null || value == null)
|
||||
throw new IllegalArgumentException("Label " + labelName + " key " + key + " and value must not be null");
|
||||
Map props = nodeProperties.containsKey(key) ? nodeProperties : MapUtil.copyAndPut(nodeProperties, key, value);
|
||||
Map<String, Object> params = map("props", props, "value", value);
|
||||
Iterator<List<Object>> result = query(mergeQuery(labelName, key, labels), params).getData().iterator();
|
||||
if (!result.hasNext())
|
||||
throw new RuntimeException("Error merging node with labels: " + labelName + " key " + key + " value " + value + " labels " + labels+ " and props: " + props + " no data returned");
|
||||
throw new RuntimeException("Error merging node with labels: " + labelName + " key " + key + " value " + value + " labels " + labels + " and props: " + props + " no data returned");
|
||||
|
||||
return addToCache(toNode(result.next()));
|
||||
}
|
||||
@@ -178,10 +195,11 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public RestRelationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> props) {
|
||||
String statement = MATCH_NODE_QUERY("n") + MATCH_NODE_QUERY("m") + " CREATE (n)-[r:`"+type.name()+"`]->(m) SET r={props} " + _QUERY_RETURN_REL;
|
||||
Map<String, Object> params = map("id_n", startNode.getId(), "id_m", endNode.getId(), "props", props);
|
||||
String statement = MATCH_NODE_QUERY("n") + MATCH_NODE_QUERY("m") + " CREATE (n)-[r:`" + type.name() + "`]->(m) SET r={props} " + _QUERY_RETURN_REL;
|
||||
Map<String, Object> params = map("id_n", startNode.getId(), "id_m", endNode.getId(), "props", props == null ? Collections.emptyMap() : props);
|
||||
CypherTransaction.Result result = runQuery(statement, params);
|
||||
if (!result.hasData()) throw new RuntimeException("Error creating relationship from "+startNode+" to "+endNode+" type "+type.name());
|
||||
if (!result.hasData())
|
||||
throw new RuntimeException("Error creating relationship from " + startNode + " to " + endNode + " type " + type.name());
|
||||
Iterator<List<Object>> it = result.getRows().iterator();
|
||||
return toRel(it.next());
|
||||
}
|
||||
@@ -191,7 +209,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
public void removeLabel(RestNode node, String label) {
|
||||
CypherTransaction.Result result = runQuery(_MATCH_NODE_QUERY + (" REMOVE n:`" + label + "` ") + _QUERY_RETURN_NODE, map("id", node.getId()));
|
||||
if (!result.hasData()) {
|
||||
throw new RuntimeException("Error removing label "+label+" from node "+node);
|
||||
throw new RuntimeException("Error removing label " + label + " from node " + node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +221,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
private Iterable<RestNode> queryForNodes(String statement, Map<String, Object> params) {
|
||||
Iterable<List<Object>> result = runQuery(statement, params).getRows();
|
||||
return new IterableWrapper<RestNode,List<Object>>(result) {
|
||||
return new IterableWrapper<RestNode, List<Object>>(result) {
|
||||
protected RestNode underlyingObjectToObject(List<Object> row) {
|
||||
return addToCache(toNode(row));
|
||||
}
|
||||
@@ -212,7 +230,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public Iterable<RestNode> getNodesByLabelAndProperty(String label, String property, Object value) {
|
||||
String statement = "MATCH (n:`" + label + "`) WHERE n.`"+property+"` = {value} " + _QUERY_RETURN_NODE;
|
||||
String statement = "MATCH (n:`" + label + "`) WHERE n.`" + property + "` = {value} " + _QUERY_RETURN_NODE;
|
||||
return queryForNodes(statement, map("value", value));
|
||||
}
|
||||
|
||||
@@ -228,16 +246,16 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public int getDegree(RestNode restNode, RelationshipType type, Direction direction) {
|
||||
String nodeDegreeQuery = "MATCH (n)" + relPattern(direction,type) + "() WHERE id(n) = {id} RETURN count(*) as degree";
|
||||
String nodeDegreeQuery = "MATCH (n)" + relPattern(direction, type) + "() WHERE id(n) = {id} RETURN count(*) as degree";
|
||||
Iterator<List<Object>> degree = runQuery(nodeDegreeQuery, map("id", restNode.getId())).getRows().iterator();
|
||||
if (!degree.hasNext()) return 0;
|
||||
return ((Number)degree.next().get(0)).intValue();
|
||||
return ((Number) degree.next().get(0)).intValue();
|
||||
}
|
||||
|
||||
private String relPattern(Direction direction, RelationshipType... types) {
|
||||
String typeString = toTypeString(types);
|
||||
String relPattern = "--";
|
||||
if (!typeString.isEmpty()) relPattern = "-[r "+typeString+"]-";
|
||||
String relPattern = "-[r]-";
|
||||
if (!typeString.isEmpty()) relPattern = "-[r " + typeString + "]-";
|
||||
if (direction == Direction.OUTGOING) {
|
||||
relPattern += ">";
|
||||
} else if (direction == Direction.INCOMING) {
|
||||
@@ -247,10 +265,10 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
}
|
||||
|
||||
private String toTypeString(RelationshipType... types) {
|
||||
if (types==null || types.length == 0) return "";
|
||||
if (types == null || types.length == 0) return "";
|
||||
StringBuilder typeString = new StringBuilder();
|
||||
for (RelationshipType type : types) {
|
||||
if (typeString.length() > 0 ) typeString.append("|");
|
||||
if (typeString.length() > 0) typeString.append("|");
|
||||
typeString.append(':').append('`').append(type.name()).append("`");
|
||||
}
|
||||
return typeString.toString();
|
||||
@@ -258,9 +276,9 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public Iterable<Relationship> getRelationships(RestNode restNode, Direction direction, RelationshipType... types) {
|
||||
String statement = _MATCH_NODE_QUERY + " MATCH (n)"+relPattern(direction,types)+"() "+_QUERY_RETURN_REL;
|
||||
String statement = _MATCH_NODE_QUERY + " MATCH (n)" + relPattern(direction, types) + "() " + _QUERY_RETURN_REL;
|
||||
CypherTransaction.Result result = runQuery(statement, map("id", restNode.getId()));
|
||||
return new IterableWrapper<Relationship,List<Object>>(result.getRows()) {
|
||||
return new IterableWrapper<Relationship, List<Object>>(result.getRows()) {
|
||||
protected Relationship underlyingObjectToObject(List<Object> row) {
|
||||
return toRel(row);
|
||||
}
|
||||
@@ -269,8 +287,8 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public void addLabels(RestNode node, Collection<String> labels) {
|
||||
String statement = _MATCH_NODE_QUERY + " SET n"+toLabelString(labels) + _QUERY_RETURN_NODE;
|
||||
runQuery(statement,map("id",node.getId()));
|
||||
String statement = _MATCH_NODE_QUERY + " SET n" + toLabelString(labels) + _QUERY_RETURN_NODE;
|
||||
runQuery(statement, map("id", node.getId()));
|
||||
RequestResult response = getRestRequest().with(node.getUri()).post("labels", labels);
|
||||
|
||||
if (response.statusOtherThan(Status.NO_CONTENT)) {
|
||||
@@ -279,50 +297,48 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
}
|
||||
|
||||
public RestRequest getRestRequest() {
|
||||
return restRequest;
|
||||
return restAPI.getRestRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction beginTx() {
|
||||
CypherTransaction tx = cypherTransaction.get();
|
||||
if (tx != null ) {
|
||||
throw new IllegalStateException("Transaction already running "+tx);
|
||||
} else {
|
||||
cypherTransaction.set(newCypherTransaction());
|
||||
return new RemoteCypherTransaction(cypherTransaction);
|
||||
}
|
||||
return txManager.beginTx();
|
||||
}
|
||||
|
||||
public RestCypherTransactionManager getTxManager() {
|
||||
return txManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends PropertyContainer> IndexHits<S> getIndex(Class<S> entityType, String indexName, String key, Object value) {
|
||||
String index = key == null ? ":`" + indexName + "`({query})" : ":`" + indexName + "`(`" + key + "`={query})";
|
||||
if (Node.class.isAssignableFrom(entityType)) {
|
||||
String statement = "start n=node"+index+ _QUERY_RETURN_NODE;
|
||||
String statement = "start n=node" + index + _QUERY_RETURN_NODE;
|
||||
CypherTransaction.Result result = runQuery(statement, map("query", value));
|
||||
return toIndexHits(result,true);
|
||||
return toIndexHits(result, true);
|
||||
}
|
||||
if (Relationship.class.isAssignableFrom(entityType)) {
|
||||
String statement = "start r=rel"+index+ _QUERY_RETURN_REL;
|
||||
String statement = "start r=rel" + index + _QUERY_RETURN_REL;
|
||||
CypherTransaction.Result result = runQuery(statement, map("query", value));
|
||||
return toIndexHits(result,false);
|
||||
return toIndexHits(result, false);
|
||||
}
|
||||
throw new IllegalStateException("Unknown index entity type "+entityType);
|
||||
throw new IllegalStateException("Unknown index entity type " + entityType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S extends PropertyContainer> IndexHits<S> queryIndex(Class<S> entityType, String indexName, String key, Object value) {
|
||||
String index = ":`" + indexName + "`({query})";
|
||||
String index = ":`" + indexName + "`({query})";
|
||||
if (Node.class.isAssignableFrom(entityType)) {
|
||||
String statement = "start n=node"+index+ _QUERY_RETURN_NODE;
|
||||
String statement = "start n=node" + index + _QUERY_RETURN_NODE;
|
||||
CypherTransaction.Result result = runQuery(statement, map("query", value));
|
||||
return toIndexHits(result,true);
|
||||
return toIndexHits(result, true);
|
||||
}
|
||||
if (Relationship.class.isAssignableFrom(entityType)) {
|
||||
String statement = "start r=rel"+index+ _QUERY_RETURN_REL;
|
||||
String statement = "start r=rel" + index + _QUERY_RETURN_REL;
|
||||
CypherTransaction.Result result = runQuery(statement, map("query", value));
|
||||
return toIndexHits(result,false);
|
||||
return toIndexHits(result, false);
|
||||
}
|
||||
throw new IllegalStateException("Unknown index entity type "+entityType);
|
||||
throw new IllegalStateException("Unknown index entity type " + entityType);
|
||||
}
|
||||
|
||||
private <S extends PropertyContainer> IndexHits<S> toIndexHits(CypherTransaction.Result result, final boolean isNode) {
|
||||
@@ -342,7 +358,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
@Override
|
||||
protected S fetchNextOrNull() {
|
||||
if (!it.hasNext()) return null;
|
||||
return (S)(isNode ? addToCache(toNode(it.next())) : toRel(it.next()));
|
||||
return (S) (isNode ? addToCache(toNode(it.next())) : toRel(it.next()));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -357,17 +373,18 @@ 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.removeFromCache(entity.getId());
|
||||
} else if (entity instanceof Relationship) {
|
||||
runQuery(_MATCH_REL_QUERY + " DELETE r", map("id", entity.getId()));
|
||||
runQuery(_MATCH_REL_QUERY + " DELETE r", map("id", entity.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPropertyOnEntity(RestEntity entity, String key, Object value) {
|
||||
if (entity instanceof Node) {
|
||||
runQuery(_MATCH_NODE_QUERY + " SET n.`"+key+"` = {value} ", map("id", entity.getId(), "value", value));
|
||||
runQuery(_MATCH_NODE_QUERY + " SET n.`" + key + "` = {value} ", map("id", entity.getId(), "value", value));
|
||||
} else if (entity instanceof Relationship) {
|
||||
runQuery(_MATCH_REL_QUERY + " SET r.`"+key+"` = {value} ", map("id", entity.getId(), "value", value));
|
||||
runQuery(_MATCH_REL_QUERY + " SET r.`" + key + "` = {value} ", map("id", entity.getId(), "value", value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,22 +401,22 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
@Override
|
||||
public void removeProperty(RestEntity entity, String key) {
|
||||
if (entity instanceof Node) {
|
||||
runQuery(_MATCH_NODE_QUERY + " REMOVE n.`"+key+"`", map("id", entity.getId()));
|
||||
runQuery(_MATCH_NODE_QUERY + " REMOVE n.`" + key + "`", map("id", entity.getId()));
|
||||
} else if (entity instanceof Relationship) {
|
||||
runQuery(_MATCH_REL_QUERY + " REMOVE r.`"+key+"`", map("id", entity.getId()));
|
||||
runQuery(_MATCH_REL_QUERY + " REMOVE r.`" + key + "`", map("id", entity.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
// todo handle within cypher tx
|
||||
@Override
|
||||
public RestNode getOrCreateNode(RestIndex<Node> index, String key, Object value, final Map<String, Object> properties, Collection<String> labels) {
|
||||
return restAPI.getOrCreateNode(index,key,value,properties,labels);
|
||||
return restAPI.getOrCreateNode(index, key, value, properties, labels);
|
||||
}
|
||||
|
||||
// todo handle within cypher tx
|
||||
@Override
|
||||
public RestRelationship getOrCreateRelationship(RestIndex<Relationship> index, String key, Object value, final RestNode start, final RestNode end, final String type, final Map<String, Object> properties) {
|
||||
return restAPI.getOrCreateRelationship(index,key,value,start,end,type,properties);
|
||||
return restAPI.getOrCreateRelationship(index, key, value, start, end, type, properties);
|
||||
}
|
||||
|
||||
public CypherResult query(String statement, Map<String, Object> params) {
|
||||
@@ -407,20 +424,19 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
}
|
||||
|
||||
private CypherTransaction.Result runQuery(String statement, Map<String, Object> params) {
|
||||
if (cypherTransaction.get() == null) {
|
||||
return newCypherTransaction().commit(statement,params);
|
||||
if (!txManager.isActive()) {
|
||||
return newCypherTransaction().commit(statement, params);
|
||||
}
|
||||
return cypherTransaction.get().send(statement,params);
|
||||
return txManager.getCypherTransaction().send(statement, params);
|
||||
}
|
||||
|
||||
private CypherTransaction newCypherTransaction() {
|
||||
public CypherTransaction newCypherTransaction() {
|
||||
return new CypherTransaction(this, CypherTransaction.ResultType.row);
|
||||
}
|
||||
|
||||
public QueryResult<Map<String, Object>> query(String statement, Map<String, Object> params, ResultConverter resultConverter) {
|
||||
final CypherResult result = query(statement, params);
|
||||
if (RestResultException.isExceptionResult(result.asMap())) throw new RestResultException(result.asMap());
|
||||
return RestQueryResult.toQueryResult(result, this, resultConverter);
|
||||
CypherTransaction.Result result = runQuery(statement, params);
|
||||
return new QueryResultBuilder<>(result, resultConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -447,7 +463,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PropertyContainer> RestIndex<T> createIndex(Class<T> type, String indexName, Map<String, String> config) {
|
||||
return restAPI.createIndex(type,indexName,config);
|
||||
return restAPI.createIndex(type, indexName, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -472,7 +488,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public void startAutoIndexingProperty(Class forClass, String s) {
|
||||
restAPI.startAutoIndexingProperty(forClass,s);
|
||||
restAPI.startAutoIndexingProperty(forClass, s);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -492,18 +508,18 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key) {
|
||||
restAPI.removeFromIndex(index,entity,key);
|
||||
restAPI.removeFromIndex(index, entity, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity) {
|
||||
restAPI.removeFromIndex(index,entity);
|
||||
restAPI.removeFromIndex(index, entity);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <T extends PropertyContainer> void addToIndex(T entity, RestIndex index, String key, Object value) {
|
||||
restAPI.addToIndex(entity,index,key,value);
|
||||
restAPI.addToIndex(entity, index, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -516,6 +532,7 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
public boolean hasToUpdate(long lastUpdate) {
|
||||
return restAPI.hasToUpdate(lastUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexInfo indexInfo(final String indexType) {
|
||||
return restAPI.indexInfo(indexType);
|
||||
@@ -533,12 +550,12 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraversalDescription createTraversalDescription() {
|
||||
public RestTraversalDescription createTraversalDescription() {
|
||||
return restAPI.createTraversalDescription();
|
||||
}
|
||||
|
||||
public String getBaseUri() {
|
||||
return restRequest.getUri();
|
||||
return restAPI.getBaseUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -550,4 +567,16 @@ public class RestAPICypherImpl implements RestAPI {
|
||||
public RestEntity createRestEntity(Map data) {
|
||||
return restAPI.createRestEntity(data);
|
||||
}
|
||||
|
||||
|
||||
public Iterable<Node> getAllNodes() {
|
||||
String statement = "MATCH (n) " + _QUERY_RETURN_NODE;
|
||||
Iterable<List<Object>> result = query(statement, null).getData();
|
||||
return new IterableWrapper<Node, List<Object>>(result) {
|
||||
@Override
|
||||
protected Node underlyingObjectToObject(List<Object> row) {
|
||||
return addToCache(toNode(row));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.neo4j.rest.graphdb.query.RestQueryResult;
|
||||
import org.neo4j.rest.graphdb.transaction.NullTransaction;
|
||||
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;
|
||||
@@ -72,6 +73,8 @@ 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<String,IndexInfo> indexInfos = new HashMap<>();
|
||||
|
||||
public RestAPIImpl(String uri) {
|
||||
this.restRequest = createRestRequest(uri, null, null);
|
||||
@@ -87,7 +90,7 @@ public class RestAPIImpl implements RestAPI {
|
||||
|
||||
@Override
|
||||
public RestIndexManager index() {
|
||||
return new RestIndexManager(this);
|
||||
return restIndexManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,14 +101,20 @@ public class RestAPIImpl implements RestAPI {
|
||||
}
|
||||
if (force == Load.FromCache) return new RestNode(RestNode.nodeUri(this, id),this);
|
||||
|
||||
BatchRestAPI batchRestAPI = new BatchRestAPI(this);
|
||||
RestNode node = batchRestAPI.getNodeById(id);
|
||||
// RequestResult response = restRequest.get("node/" + id);
|
||||
// if (response.statusIs(Status.NOT_FOUND)) {
|
||||
// throw new NotFoundException("" + id);
|
||||
// }
|
||||
// Collection<String> labels = getNodeLabels(id);
|
||||
// RestNode node = new RestNode(id, labels, (Map<String, Object>) response.toMap(), this);
|
||||
// BatchRestAPI batchRestAPI = new BatchRestAPI(this);
|
||||
// RestNode node = batchRestAPI.getNodeById(id);
|
||||
RequestResult response = restRequest.get("node/" + id);
|
||||
if (response.statusIs(Status.NOT_FOUND)) {
|
||||
throw new NotFoundException("" + id);
|
||||
}
|
||||
RestNode node;
|
||||
Map<String, Object> data = (Map<String, Object>) response.toMap();
|
||||
if (response.isMap() && data.containsKey("metadata")) {
|
||||
node = new RestNode(data, this);
|
||||
} else {
|
||||
Collection<String> labels = getNodeLabels(id);
|
||||
node = new RestNode(id, labels, data, this);
|
||||
}
|
||||
return entityCache.addToCache(node);
|
||||
}
|
||||
|
||||
@@ -119,6 +128,11 @@ public class RestAPIImpl implements RestAPI {
|
||||
return entityCache.getNode(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFromCache(long id) {
|
||||
entityCache.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestNode getNodeById(long id) {
|
||||
return getNodeById(id, Load.FromServer);
|
||||
@@ -442,8 +456,8 @@ public class RestAPIImpl implements RestAPI {
|
||||
|
||||
|
||||
@Override
|
||||
public TraversalDescription createTraversalDescription() {
|
||||
return new RestTraversal();
|
||||
public RestTraversalDescription createTraversalDescription() {
|
||||
return RestTraversal.description();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -521,8 +535,14 @@ public class RestAPIImpl implements RestAPI {
|
||||
}
|
||||
@Override
|
||||
public IndexInfo indexInfo(final String indexType) {
|
||||
IndexInfo indexInfo = indexInfos.get(indexType);
|
||||
if (indexInfo != null && !indexInfo.isExpired()) {
|
||||
|
||||
}
|
||||
RequestResult response = restRequest.get("index/" + encode(indexType));
|
||||
return new RetrievedIndexInfo(response);
|
||||
indexInfo = new RetrievedIndexInfo(response);
|
||||
indexInfos.put(indexType,indexInfo);
|
||||
return indexInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -147,28 +147,4 @@ public class BatchRestAPI {
|
||||
}
|
||||
return uri + "/" + suffix;
|
||||
}
|
||||
|
||||
|
||||
private static class BatchIndexInfo implements IndexInfo {
|
||||
|
||||
@Override
|
||||
public boolean checkConfig(String indexName, Map<String, String> config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] indexNames() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String indexName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getConfig(String name) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,4 +33,6 @@ public interface IndexInfo {
|
||||
boolean exists(String indexName);
|
||||
|
||||
Map<String, String> getConfig(String name);
|
||||
|
||||
boolean isExpired();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ package org.neo4j.rest.graphdb.index;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.neo4j.rest.graphdb.RequestResult;
|
||||
|
||||
@@ -33,10 +34,14 @@ import com.sun.jersey.api.client.ClientResponse;
|
||||
*/
|
||||
public class RetrievedIndexInfo implements IndexInfo {
|
||||
private Map<String, ?> indexInfo;
|
||||
private long expired;
|
||||
|
||||
public RetrievedIndexInfo(RequestResult response) {
|
||||
if (response.statusIs(ClientResponse.Status.NO_CONTENT)) this.indexInfo = Collections.emptyMap();
|
||||
else this.indexInfo = (Map<String, ?>) response.toMap();
|
||||
else {
|
||||
this.indexInfo = (Map<String, ?>) response.toMap();
|
||||
this.expired = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,4 +77,9 @@ public class RetrievedIndexInfo implements IndexInfo {
|
||||
public Map<String, String> getConfig(String name) {
|
||||
return (Map<String, String>) indexInfo.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > expired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import com.sun.jersey.api.client.ClientResponse;
|
||||
import org.neo4j.helpers.collection.IterableWrapper;
|
||||
import org.neo4j.helpers.collection.IteratorWrapper;
|
||||
import org.neo4j.rest.graphdb.*;
|
||||
import org.springframework.data.neo4j.mapping.RelationshipResult;
|
||||
|
||||
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.helpers.collection.MapUtil.stringMap;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
@@ -144,14 +145,14 @@ public class CypherTransaction {
|
||||
add(statement,params);
|
||||
List<Result> results = send(transactionUrl());
|
||||
if (results.size() > 0) return results.get(results.size() - 1);
|
||||
else throw new RuntimeException("No Results after single send");
|
||||
throw new CypherTransactionExecutionException("Error Sending",asList(new Statement(statement,params,type)),errors("No.Results","No Results after single send"));
|
||||
}
|
||||
|
||||
public Result commit(String statement, Map<String,Object> params) {
|
||||
add(statement,params);
|
||||
List<Result> results = commit();
|
||||
if (results.size() > 0) return results.get(results.size() - 1);
|
||||
else throw new RuntimeException("No Results after single commit");
|
||||
else throw new CypherTransactionExecutionException("Error Sending",asList(new Statement(statement,params,type)),errors("No.Results","No Results after single commit"));
|
||||
}
|
||||
|
||||
public List<Result> send() {
|
||||
@@ -160,6 +161,7 @@ public class CypherTransaction {
|
||||
|
||||
public List<Result> commit() {
|
||||
try {
|
||||
if (statements.isEmpty()) add("return 1",null); // TODO hacking workaround b/c of periodic commit check in server accesses the first of an empty statement list with an NPE
|
||||
return send(commitUrl());
|
||||
} finally {
|
||||
commitUrl = null;
|
||||
@@ -170,16 +172,22 @@ public class CypherTransaction {
|
||||
try {
|
||||
RequestResult result = request.post(url, map("statements", statements));
|
||||
if (result.statusIs(Response.Status.OK) || result.statusIs(Response.Status.CREATED)) {
|
||||
return Result.toResults(handleResult(result), new ArrayList<>(statements), type);
|
||||
ArrayList<Statement> statementsCopy = new ArrayList<>(statements);
|
||||
return Result.toResults(handleResult(result,statementsCopy), statementsCopy, type);
|
||||
} else {
|
||||
throw new RuntimeException("Error executing statements: " + result.getStatus() +
|
||||
" " + result.getText());
|
||||
List<Map<String, String>> errors = errors("Http." + result.getStatus(), result.getText());
|
||||
throw new CypherTransactionExecutionException("Error executing statements: " + result.getStatus() +
|
||||
" " + result.getText(),statements, errors);
|
||||
}
|
||||
} finally {
|
||||
statements.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private List<Map<String, String>> errors(String code, String message) {
|
||||
return asList(stringMap("code", code, "message", message));
|
||||
}
|
||||
|
||||
public void rollback() {
|
||||
if (transactionUrl != null) {
|
||||
request.delete(transactionUrl);
|
||||
@@ -197,10 +205,10 @@ public class CypherTransaction {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Map> handleResult(RequestResult result) {
|
||||
private List<Map> handleResult(RequestResult result, ArrayList<Statement> statements) {
|
||||
Map<?, ?> resultData = result.toMap();
|
||||
Object errors = resultData.get("errors");
|
||||
if (errors != null && !((Collection)errors).isEmpty()) throw new RuntimeException("Error executing cypher statements "+errors);
|
||||
List<Map<String,String>> errors = (List<Map<String, String>>) resultData.get("errors");
|
||||
if (errors != null && !errors.isEmpty()) throw new CypherTransactionExecutionException("Error executing cypher statements ",statements, errors);
|
||||
if (result.statusIs(ClientResponse.Status.CREATED)) transactionUrl = result.getLocation();
|
||||
commitUrl = (String) resultData.get("commit");
|
||||
return (List<Map>) resultData.get("results");
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.neo4j.rest.graphdb.query;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 28.09.14
|
||||
*/
|
||||
public class CypherTransactionExecutionException extends RuntimeException {
|
||||
private final List<CypherTransaction.Statement> statements;
|
||||
private final List<Map<String, String>> errors;
|
||||
|
||||
public CypherTransactionExecutionException(String msg, List<CypherTransaction.Statement> statements, List<Map<String,String>> errors) {
|
||||
super(msg + errors.toString());
|
||||
this.statements = statements;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public List<CypherTransaction.Statement> getStatements() {
|
||||
return statements;
|
||||
}
|
||||
|
||||
public List<Map<String, String>> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
public boolean contains(String code, String message) {
|
||||
for (Map<String, String> error : errors) {
|
||||
if (!code.equals(error.get("code"))) continue;
|
||||
String msg = error.get("message");
|
||||
if (msg != null && msg.contains(message)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.neo4j.rest.graphdb.query;
|
||||
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.neo4j.rest.graphdb.RestAPICypherImpl;
|
||||
import org.neo4j.rest.graphdb.transaction.RemoteCypherTransaction;
|
||||
|
||||
import javax.transaction.*;
|
||||
import javax.transaction.Transaction;
|
||||
import javax.transaction.xa.XAResource;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 28.09.14
|
||||
*/
|
||||
public class RestCypherTransactionManager implements TransactionManager, Transaction {
|
||||
private static final ThreadLocal<RemoteCypherTransaction> cypherTransaction = new ThreadLocal<>();
|
||||
private final RestAPICypherImpl restAPICypher;
|
||||
|
||||
public RestCypherTransactionManager(RestAPICypherImpl restAPICypher) {
|
||||
this.restAPICypher = restAPICypher;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void begin() throws NotSupportedException, SystemException {
|
||||
beginTx();
|
||||
}
|
||||
|
||||
public org.neo4j.graphdb.Transaction beginTx() {
|
||||
RemoteCypherTransaction tx = cypherTransaction.get();
|
||||
if (tx != null && tx.isActive()) {
|
||||
tx.beginInner();
|
||||
} else {
|
||||
CypherTransaction newTx = restAPICypher.newCypherTransaction();
|
||||
tx = new RemoteCypherTransaction(newTx);
|
||||
cypherTransaction.set(tx);
|
||||
}
|
||||
return tx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() throws HeuristicMixedException, HeuristicRollbackException, IllegalStateException, RollbackException, SecurityException, SystemException {
|
||||
RemoteCypherTransaction tx = getRemoteCypherTransaction();
|
||||
tx.success();
|
||||
tx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enlistResource(XAResource xaRes) throws IllegalStateException, RollbackException, SystemException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerSynchronization(Synchronization synch) throws IllegalStateException, RollbackException, SystemException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() throws SystemException {
|
||||
RemoteCypherTransaction tx = getRemoteCypherTransaction();
|
||||
if (tx == null || !tx.isActive()) return Status.STATUS_NO_TRANSACTION;
|
||||
return tx.getStatus();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Transaction getTransaction() throws SystemException {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws IllegalStateException, SecurityException, SystemException {
|
||||
RemoteCypherTransaction tx = getRemoteCypherTransaction();
|
||||
tx.failure();
|
||||
tx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() throws IllegalStateException, SystemException {
|
||||
getRemoteCypherTransaction().failure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransactionTimeout(int seconds) throws SystemException {
|
||||
// todo
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transaction suspend() throws SystemException {
|
||||
return this;
|
||||
} // todo we could implement suspend
|
||||
|
||||
@Override
|
||||
public void resume(Transaction tx) throws IllegalStateException, InvalidTransactionException, SystemException {
|
||||
// todo we could implement suspend
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
RemoteCypherTransaction tx = getRemoteCypherTransaction();
|
||||
return tx !=null && tx.isActive();
|
||||
}
|
||||
|
||||
public CypherTransaction getCypherTransaction() {
|
||||
return getRemoteCypherTransaction().getTransaction();
|
||||
}
|
||||
|
||||
public RemoteCypherTransaction getRemoteCypherTransaction() {
|
||||
return cypherTransaction.get();
|
||||
}
|
||||
}
|
||||
@@ -22,19 +22,31 @@ package org.neo4j.rest.graphdb.transaction;
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.neo4j.rest.graphdb.query.CypherTransaction;
|
||||
|
||||
import javax.transaction.Status;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.neo4j.helpers.collection.MapUtil.map;
|
||||
|
||||
public class RemoteCypherTransaction implements Transaction {
|
||||
|
||||
int status = Status.STATUS_NO_TRANSACTION;
|
||||
boolean success, failure;
|
||||
ThreadLocal<CypherTransaction> tx;
|
||||
CypherTransaction tx;
|
||||
AtomicInteger innerCounter = new AtomicInteger(1);
|
||||
|
||||
public RemoteCypherTransaction(ThreadLocal<CypherTransaction> tx) {
|
||||
public RemoteCypherTransaction(CypherTransaction tx) {
|
||||
this.tx = tx;
|
||||
status = Status.STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
public void beginInner() {
|
||||
innerCounter.incrementAndGet();
|
||||
}
|
||||
|
||||
public void success() {
|
||||
this.success = true;
|
||||
if (!failure) status = Status.STATUS_COMMITTING; // ???
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
@@ -43,24 +55,31 @@ public class RemoteCypherTransaction implements Transaction {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (tx() != null && innerCounter.decrementAndGet() > 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (success && !failure)
|
||||
if (success && !failure) {
|
||||
tx().commit();
|
||||
else
|
||||
status = Status.STATUS_COMMITTED;
|
||||
}
|
||||
else {
|
||||
tx().rollback();
|
||||
status = Status.STATUS_ROLLEDBACK;
|
||||
}
|
||||
} finally {
|
||||
tx.set(null);
|
||||
tx = null;
|
||||
}
|
||||
}
|
||||
|
||||
private CypherTransaction tx() {
|
||||
CypherTransaction cypherTransaction = tx.get();
|
||||
if (cypherTransaction == null) throw new IllegalStateException("No transaction active");
|
||||
return cypherTransaction;
|
||||
if (tx == null) throw new IllegalStateException("No transaction active");
|
||||
return tx;
|
||||
}
|
||||
|
||||
public void failure() {
|
||||
this.failure = true;
|
||||
status = Status.STATUS_MARKED_ROLLBACK;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,4 +97,17 @@ public class RemoteCypherTransaction implements Transaction {
|
||||
public Lock acquireReadLock(PropertyContainer propertyContainer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
public CypherTransaction getTransaction() {
|
||||
return tx;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return tx != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.neo4j.rest;
|
||||
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.neo4j.graphdb.index.Index;
|
||||
import org.neo4j.rest.graphdb.CypherRestGraphDatabase;
|
||||
import org.neo4j.rest.graphdb.RestAPI;
|
||||
import org.neo4j.rest.graphdb.RestAPIImpl;
|
||||
import org.neo4j.rest.graphdb.entity.RestNode;
|
||||
import org.neo4j.rest.graphdb.index.RestIndex;
|
||||
import org.neo4j.rest.graphdb.index.RestIndexManager;
|
||||
import org.neo4j.rest.graphdb.transaction.NullTransaction;
|
||||
import org.neo4j.rest.graphdb.transaction.NullTransactionManager;
|
||||
import org.neo4j.rest.graphdb.util.Config;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.neo4j.conversion.DefaultConverter;
|
||||
import org.springframework.data.neo4j.conversion.ResultConverter;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
|
||||
import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryEngine;
|
||||
import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
|
||||
|
||||
import javax.transaction.TransactionManager;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class SpringCypherRestGraphDatabase extends CypherRestGraphDatabase implements GraphDatabase {
|
||||
|
||||
private ConversionService conversionService;
|
||||
private ResultConverter resultConverter;
|
||||
private SchemaIndexProvider schemaIndexProvider;
|
||||
|
||||
public SpringCypherRestGraphDatabase(RestAPI api){
|
||||
super(api);
|
||||
schemaIndexProvider = new SchemaIndexProvider(this);
|
||||
}
|
||||
|
||||
public SpringCypherRestGraphDatabase(String uri) {
|
||||
this( new RestAPIImpl( uri ) );
|
||||
}
|
||||
|
||||
public SpringCypherRestGraphDatabase(String uri, String user, String password) {
|
||||
this(new RestAPIImpl( uri, user, password ));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createNode(Map<String, Object> props, Collection<String> labels) {
|
||||
RestAPI restAPI = super.getRestAPI();
|
||||
return restAPI.createNode(props,labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getOrCreateNode(String indexName, String key, Object value, final Map<String, Object> properties, Collection<String> labels) {
|
||||
if (indexName ==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+ indexName +" key "+key+" value must not be null");
|
||||
final RestIndex<Node> nodeIndex = index().forNodes(indexName);
|
||||
return getRestAPI().getOrCreateNode(nodeIndex, key, value, properties, labels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node merge(String labelName, String key, Object value, final Map<String, Object> nodeProperties, Collection<String> labels) {
|
||||
return getRestAPI().merge(labelName,key,value,nodeProperties, labels);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Relationship getOrCreateRelationship(String indexName, String key, Object value, Node startNode, Node endNode, String type, Map<String, Object> properties) {
|
||||
@SuppressWarnings("unchecked") final RestIndex<Relationship> relIndex = (RestIndex<Relationship>) index().forRelationships(indexName);
|
||||
return getRestAPI().getOrCreateRelationship(relIndex,key,value,(RestNode) startNode,(RestNode) endNode,type, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Relationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> properties) {
|
||||
return super.getRestAPI().createRelationship(startNode, endNode, type, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends PropertyContainer> Index<T> getIndex(String indexName) {
|
||||
try {
|
||||
return super.getRestAPI().getIndex(indexName);
|
||||
} catch (IllegalArgumentException iea) {
|
||||
throw new NoSuchIndexException(indexName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends PropertyContainer> Index<T> createIndex(Class<T> type, String indexName, org.springframework.data.neo4j.support.index.IndexType indexType) {
|
||||
return super.getRestAPI().createIndex(type, indexName, indexType.getConfig());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public CypherQueryEngine queryEngine(final ResultConverter resultConverter) {
|
||||
return new SpringRestCypherQueryEngine(getRestAPI(),resultConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CypherQueryEngine queryEngine() {
|
||||
return queryEngine(createResultConverter());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
private ResultConverter createResultConverter() {
|
||||
if (resultConverter!=null) return resultConverter;
|
||||
if (conversionService != null) {
|
||||
this.resultConverter = new ConversionServiceQueryResultConverter(conversionService);
|
||||
} else {
|
||||
this.resultConverter = new DefaultConverter();
|
||||
}
|
||||
return resultConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transactionIsRunning() {
|
||||
return super.getTxManager().isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionManager getTransactionManager() {
|
||||
return super.getTxManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Node node) {
|
||||
removeFromIndexes(node); // todo should we do this by default?
|
||||
node.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Relationship relationship) {
|
||||
removeFromIndexes(relationship); // todo should we do this by default, even if it might not be indexed?
|
||||
relationship.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResultConverter(ResultConverter resultConverter) {
|
||||
this.resultConverter = resultConverter;
|
||||
}
|
||||
|
||||
private void removeFromIndexes(Node node) {
|
||||
final RestIndexManager indexManager = index();
|
||||
for (String indexName : indexManager.nodeIndexNames()) {
|
||||
indexManager.forNodes(indexName).remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFromIndexes(Relationship relationship) {
|
||||
final RestIndexManager indexManager = index();
|
||||
for (String indexName : indexManager.relationshipIndexNames()) {
|
||||
indexManager.forRelationships(indexName).remove(relationship);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public class RestEntityTests extends RestTestBase {
|
||||
|
||||
@Test
|
||||
public void testSetProperty() {
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node node = createNode();
|
||||
node.setProperty("name", "test");
|
||||
Node node2 = restGraphDatabase.getNodeById(node.getId());
|
||||
assertEquals("test", node2.getProperty("name"));
|
||||
@@ -40,7 +40,7 @@ public class RestEntityTests extends RestTestBase {
|
||||
|
||||
@Test
|
||||
public void testSetStringArrayProperty() {
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node node = createNode();
|
||||
node.setProperty("name", new String[]{"test"});
|
||||
Node node2 = restGraphDatabase.getNodeById(node.getId());
|
||||
Assert.assertArrayEquals( new String[]{"test"}, (String[])node2.getProperty( "name" ) );
|
||||
@@ -48,7 +48,7 @@ public class RestEntityTests extends RestTestBase {
|
||||
@Test
|
||||
public void testSetDoubleArrayProperty() {
|
||||
double[] data = {0, 1, 2};
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node node = createNode();
|
||||
node.setProperty("data", data);
|
||||
Node node2 = restGraphDatabase.getNodeById(node.getId());
|
||||
Assert.assertTrue("same double array",Arrays.equals( data, (double[])node2.getProperty( "data" ) ));
|
||||
@@ -56,7 +56,7 @@ public class RestEntityTests extends RestTestBase {
|
||||
|
||||
@Test
|
||||
public void testRemoveProperty() {
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node node = createNode();
|
||||
node.setProperty( "name", "test" );
|
||||
assertEquals("test", node.getProperty("name"));
|
||||
node.removeProperty( "name" );
|
||||
@@ -65,7 +65,7 @@ public class RestEntityTests extends RestTestBase {
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void testRemoveNode() {
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node node = createNode();
|
||||
node.setProperty( "name", "test" );
|
||||
final long nodeId = node.getId();
|
||||
assertEquals("test", node.getProperty("name"));
|
||||
@@ -75,8 +75,8 @@ public class RestEntityTests extends RestTestBase {
|
||||
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void testRemoveRelationship() {
|
||||
Node refNode = restGraphDatabase.createNode();
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node refNode = createNode();
|
||||
Node node = createNode();
|
||||
Relationship rel = restGraphDatabase.createRelationship(refNode, node, Type.TEST, map("name","test"));
|
||||
final long relId = rel.getId();
|
||||
assertEquals("test", rel.getProperty("name"));
|
||||
@@ -87,8 +87,8 @@ public class RestEntityTests extends RestTestBase {
|
||||
|
||||
@Test
|
||||
public void testSetPropertyOnRelationship() {
|
||||
Node refNode = restGraphDatabase.createNode();
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node refNode = createNode();
|
||||
Node node = createNode();
|
||||
Relationship rel = refNode.createRelationshipTo( node, Type.TEST );
|
||||
rel.setProperty( "name", "test" );
|
||||
assertEquals("test", rel.getProperty("name"));
|
||||
@@ -98,8 +98,8 @@ public class RestEntityTests extends RestTestBase {
|
||||
|
||||
@Test
|
||||
public void testRemovePropertyOnRelationship() {
|
||||
Node refNode = restGraphDatabase.createNode();
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node refNode = createNode();
|
||||
Node node = createNode();
|
||||
Relationship rel = refNode.createRelationshipTo( node, Type.TEST );
|
||||
rel.setProperty( "name", "test" );
|
||||
assertEquals("test", rel.getProperty("name"));
|
||||
|
||||
@@ -26,21 +26,21 @@ public class RestGraphDbTests extends RestTestBase {
|
||||
|
||||
@Test
|
||||
public void testGetRefNode() {
|
||||
Node refNode = restGraphDatabase.createNode();
|
||||
Node refNode = createNode();
|
||||
Node nodeById = restGraphDatabase.getNodeById( refNode.getId() );
|
||||
Assert.assertEquals( refNode, nodeById );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNode() {
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node node = createNode();
|
||||
Assert.assertEquals( node, restGraphDatabase.getNodeById( node.getId() ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateRelationship() {
|
||||
Node refNode = restGraphDatabase.createNode();
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node refNode = createNode();
|
||||
Node node = createNode();
|
||||
Relationship rel = refNode.createRelationshipTo( node, Type.TEST );
|
||||
Relationship foundRelationship = IsRelationshipToNodeMatcher.relationshipFromTo( refNode.getRelationships( Type.TEST, Direction.OUTGOING ), refNode, node );
|
||||
Assert.assertNotNull( "found relationship", foundRelationship );
|
||||
@@ -53,8 +53,8 @@ public class RestGraphDbTests extends RestTestBase {
|
||||
|
||||
@Test
|
||||
public void testBasic() {
|
||||
Node refNode = restGraphDatabase.createNode();
|
||||
Node node = restGraphDatabase.createNode();
|
||||
Node refNode = createNode();
|
||||
Node node = createNode();
|
||||
Relationship rel = refNode.createRelationshipTo( node,
|
||||
DynamicRelationshipType.withName( "TEST" ) );
|
||||
rel.setProperty( "date", new Date().getTime() );
|
||||
|
||||
@@ -111,23 +111,23 @@ public class RestIndexTests extends RestTestBase {
|
||||
}
|
||||
|
||||
private Index<Node> nodeIndex() {
|
||||
return restGraphDatabase.index().forNodes(NODE_INDEX_NAME);
|
||||
return index().forNodes(NODE_INDEX_NAME);
|
||||
}
|
||||
|
||||
private RelationshipIndex relationshipIndex() {
|
||||
return restGraphDatabase.index().forRelationships(REL_INDEX_NAME);
|
||||
return index().forRelationships(REL_INDEX_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNodeIndexIsListed() {
|
||||
nodeIndex().add(node(), "name", "test");
|
||||
Assert.assertTrue("node index name listed", Arrays.asList(restGraphDatabase.index().nodeIndexNames()).contains(NODE_INDEX_NAME));
|
||||
Assert.assertTrue("node index name listed", Arrays.asList(index().nodeIndexNames()).contains(NODE_INDEX_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelationshipIndexIsListed() {
|
||||
relationshipIndex().add(relationship(), "name", "test");
|
||||
Assert.assertTrue("relationship index name listed", Arrays.asList(restGraphDatabase.index().relationshipIndexNames()).contains(REL_INDEX_NAME));
|
||||
Assert.assertTrue("relationship index name listed", Arrays.asList(index().relationshipIndexNames()).contains(REL_INDEX_NAME));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,10 +20,12 @@ package org.springframework.data.neo4j.rest.support;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.GraphDatabaseService;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.index.IndexManager;
|
||||
import org.neo4j.rest.graphdb.ExecutingRestRequest;
|
||||
import org.neo4j.rest.graphdb.RequestResult;
|
||||
import org.neo4j.server.NeoServer;
|
||||
@@ -31,6 +33,8 @@ import org.neo4j.server.WrappingNeoServerBootstrapper;
|
||||
import org.neo4j.server.configuration.Configurator;
|
||||
import org.neo4j.server.configuration.ServerConfigurator;
|
||||
import org.neo4j.test.ImpermanentGraphDatabase;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase;
|
||||
import org.springframework.data.neo4j.rest.SpringRestGraphDatabase;
|
||||
|
||||
import java.util.Iterator;
|
||||
@@ -40,7 +44,7 @@ import static org.junit.Assert.assertEquals;
|
||||
public class RestTestBase {
|
||||
|
||||
protected static ImpermanentGraphDatabase db;
|
||||
protected SpringRestGraphDatabase restGraphDatabase;
|
||||
protected GraphDatabase restGraphDatabase;
|
||||
private static final String HOSTNAME = "127.0.0.1";
|
||||
public static final int PORT = 7470;
|
||||
protected static NeoServer neoServer = null;
|
||||
@@ -78,8 +82,13 @@ public class RestTestBase {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
cleanDb();
|
||||
restGraphDatabase = new SpringRestGraphDatabase(SERVER_ROOT_URI);
|
||||
refNode = restGraphDatabase.createNode();
|
||||
// restGraphDatabase = new SpringRestGraphDatabase(SERVER_ROOT_URI);
|
||||
restGraphDatabase = new SpringCypherRestGraphDatabase(SERVER_ROOT_URI);
|
||||
refNode = createNode();
|
||||
}
|
||||
|
||||
public Node createNode() {
|
||||
return restGraphDatabase.createNode(null,null);
|
||||
}
|
||||
|
||||
public static void cleanDb() {
|
||||
@@ -100,7 +109,11 @@ public class RestTestBase {
|
||||
protected Relationship relationship() {
|
||||
Iterator<Relationship> it = node().getRelationships(Direction.OUTGOING).iterator();
|
||||
if (it.hasNext()) return it.next();
|
||||
return node().createRelationshipTo(restGraphDatabase.createNode(), Type.TEST);
|
||||
return node().createRelationshipTo(createNode(), Type.TEST);
|
||||
}
|
||||
|
||||
protected IndexManager index() {
|
||||
return ((GraphDatabaseService)restGraphDatabase).index();
|
||||
}
|
||||
|
||||
protected Node node() {
|
||||
|
||||
@@ -23,20 +23,23 @@ import org.neo4j.kernel.GraphDatabaseAPI;
|
||||
import org.neo4j.kernel.impl.transaction.SpringTransactionManager;
|
||||
import org.neo4j.kernel.impl.transaction.UserTransactionImpl;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
import org.springframework.transaction.jta.UserTransactionAdapter;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
public class JtaTransactionManagerFactoryBean implements FactoryBean<JtaTransactionManager>
|
||||
{
|
||||
private final JtaTransactionManager jtaTransactionManager;
|
||||
|
||||
public JtaTransactionManagerFactoryBean( GraphDatabaseService gds ) throws Exception
|
||||
public JtaTransactionManagerFactoryBean( GraphDatabaseService gds )
|
||||
{
|
||||
jtaTransactionManager = create( gds );
|
||||
}
|
||||
|
||||
@Override
|
||||
public JtaTransactionManager getObject() throws Exception
|
||||
public JtaTransactionManager getObject()
|
||||
{
|
||||
return jtaTransactionManager;
|
||||
}
|
||||
@@ -53,21 +56,31 @@ public class JtaTransactionManagerFactoryBean implements FactoryBean<JtaTransact
|
||||
return true;
|
||||
}
|
||||
|
||||
private JtaTransactionManager create( GraphDatabaseService gds ) throws Exception
|
||||
private JtaTransactionManager create( GraphDatabaseService gds )
|
||||
{
|
||||
if ( !(gds instanceof GraphDatabaseAPI) )
|
||||
{
|
||||
return createNullJtaTransactionManager();
|
||||
if ( gds instanceof GraphDatabase) {
|
||||
return createJtaTransactionManager( (GraphDatabase)gds );
|
||||
}
|
||||
if ( gds instanceof GraphDatabaseAPI)
|
||||
try
|
||||
{
|
||||
return createJtaTransactionManager( gds );
|
||||
}
|
||||
catch ( RuntimeException e )
|
||||
{
|
||||
if (e.getCause() instanceof NoSuchMethodException)
|
||||
return createJtaTransactionManagerForOnePointSeven( gds );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return createJtaTransactionManager( gds );
|
||||
}
|
||||
catch ( NoSuchMethodException e )
|
||||
{
|
||||
return createJtaTransactionManagerForOnePointSeven( gds );
|
||||
}
|
||||
return createNullJtaTransactionManager();
|
||||
}
|
||||
|
||||
private JtaTransactionManager createJtaTransactionManager(GraphDatabase gdb)
|
||||
{
|
||||
TransactionManager transactionManager = gdb.getTransactionManager();
|
||||
UserTransaction userTransaction = new UserTransactionAdapter( transactionManager );
|
||||
|
||||
return new JtaTransactionManager( userTransaction, transactionManager );
|
||||
}
|
||||
|
||||
private JtaTransactionManager createNullJtaTransactionManager()
|
||||
@@ -78,7 +91,7 @@ public class JtaTransactionManagerFactoryBean implements FactoryBean<JtaTransact
|
||||
return new JtaTransactionManager( userTransaction, transactionManager );
|
||||
}
|
||||
|
||||
private JtaTransactionManager createJtaTransactionManagerForOnePointSeven( GraphDatabaseService gds ) throws Exception
|
||||
private JtaTransactionManager createJtaTransactionManagerForOnePointSeven( GraphDatabaseService gds )
|
||||
{
|
||||
TransactionManager transactionManager = createTransactionManagerForOnePointSeven( gds );
|
||||
UserTransaction userTransaction = createUserTransactionForOnePointSeven( gds );
|
||||
@@ -86,7 +99,7 @@ public class JtaTransactionManagerFactoryBean implements FactoryBean<JtaTransact
|
||||
return new JtaTransactionManager( userTransaction, transactionManager );
|
||||
}
|
||||
|
||||
private JtaTransactionManager createJtaTransactionManager( GraphDatabaseService gds ) throws Exception
|
||||
private JtaTransactionManager createJtaTransactionManager( GraphDatabaseService gds )
|
||||
{
|
||||
TransactionManager transactionManager = createTransactionManagerForOnePointEight( gds );
|
||||
UserTransaction userTransaction = createUserTransactionForOnePointEight( gds );
|
||||
@@ -94,30 +107,34 @@ public class JtaTransactionManagerFactoryBean implements FactoryBean<JtaTransact
|
||||
return new JtaTransactionManager( userTransaction, transactionManager );
|
||||
}
|
||||
|
||||
private TransactionManager createTransactionManagerForOnePointSeven( GraphDatabaseService gds ) throws Exception
|
||||
private TransactionManager createTransactionManagerForOnePointSeven( GraphDatabaseService gds )
|
||||
{
|
||||
return createDynamically( SpringTransactionManager.class, GraphDatabaseService.class, gds );
|
||||
}
|
||||
|
||||
private UserTransaction createUserTransactionForOnePointSeven( GraphDatabaseService gds ) throws Exception
|
||||
private UserTransaction createUserTransactionForOnePointSeven( GraphDatabaseService gds )
|
||||
{
|
||||
TransactionManager txManager = ((GraphDatabaseAPI) gds).getDependencyResolver().resolveDependency(TransactionManager.class);
|
||||
return createDynamically( UserTransactionImpl.class, TransactionManager.class, txManager );
|
||||
}
|
||||
|
||||
private TransactionManager createTransactionManagerForOnePointEight( GraphDatabaseService gds ) throws Exception
|
||||
private TransactionManager createTransactionManagerForOnePointEight( GraphDatabaseService gds )
|
||||
{
|
||||
return createDynamically( SpringTransactionManager.class, GraphDatabaseAPI.class, gds );
|
||||
}
|
||||
|
||||
private UserTransaction createUserTransactionForOnePointEight( GraphDatabaseService gds ) throws Exception
|
||||
private UserTransaction createUserTransactionForOnePointEight( GraphDatabaseService gds )
|
||||
{
|
||||
return createDynamically( UserTransactionImpl.class, GraphDatabaseAPI.class, gds );
|
||||
}
|
||||
|
||||
private <T> T createDynamically( Class<T> requiredClass, Class<?> argumentClass, Object gds ) throws Exception
|
||||
private <T> T createDynamically( Class<T> requiredClass, Class<?> argumentClass, Object gds )
|
||||
{
|
||||
return requiredClass.getDeclaredConstructor( argumentClass ).newInstance( gds );
|
||||
try {
|
||||
return requiredClass.getDeclaredConstructor( argumentClass ).newInstance( gds );
|
||||
} catch (InstantiationException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Error accessing constructor of class "+requiredClass+ " for parameter type "+argumentClass,e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentat
|
||||
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
|
||||
import org.springframework.data.support.IsNewStrategyFactory;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
|
||||
|
||||
import javax.validation.Validator;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -69,7 +71,7 @@ import javax.enterprise.inject.Produces;
|
||||
* @author Thomas Risberg
|
||||
*/
|
||||
@Configuration
|
||||
public abstract class Neo4jConfiguration {
|
||||
public abstract class Neo4jConfiguration { // implements TransactionManagementConfigurer {
|
||||
private GraphDatabaseService graphDatabaseService;
|
||||
|
||||
private ConversionService conversionService;
|
||||
@@ -112,7 +114,7 @@ public abstract class Neo4jConfiguration {
|
||||
factoryBean.setRelationshipTypeRepresentationStrategy(relationshipTypeRepresentationStrategy());
|
||||
factoryBean.setRelationshipEntityInstantiator(graphRelationshipInstantiator());
|
||||
|
||||
factoryBean.setTransactionManager(neo4jTransactionManager());
|
||||
factoryBean.setTransactionManager(neo4jTransactionManager(getGraphDatabaseService()));
|
||||
factoryBean.setGraphDatabase(graphDatabase());
|
||||
factoryBean.setIsNewStrategyFactory(isNewStrategyFactory());
|
||||
factoryBean.setTypeSafetyPolicy(typeSafetyPolicy());
|
||||
@@ -237,10 +239,17 @@ public abstract class Neo4jConfiguration {
|
||||
|
||||
@Bean(name = {"neo4jTransactionManager","transactionManager"})
|
||||
@Qualifier("neo4jTransactionManager")
|
||||
public PlatformTransactionManager neo4jTransactionManager() throws Exception {
|
||||
return new JtaTransactionManagerFactoryBean(getGraphDatabaseService()).getObject();
|
||||
@DependsOn("graphDatabaseService")
|
||||
public PlatformTransactionManager neo4jTransactionManager(GraphDatabaseService graphDatabaseService) {
|
||||
JtaTransactionManagerFactoryBean jtaTransactionManagerFactoryBean = new JtaTransactionManagerFactoryBean(graphDatabaseService);
|
||||
return jtaTransactionManagerFactoryBean.getObject();
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public PlatformTransactionManager annotationDrivenTransactionManager() {
|
||||
// return neo4jTransactionManager();
|
||||
// }
|
||||
|
||||
@Bean
|
||||
public EntityIndexCreator entityIndexCreator() throws Exception {
|
||||
return new EntityIndexCreator(
|
||||
|
||||
Reference in New Issue
Block a user