DATAGRAPH-483 REST Transaction support

This commit is contained in:
Michael Hunger
2014-09-28 23:51:28 +02:00
parent c2f1230ed4
commit 0adee415b2
19 changed files with 801 additions and 189 deletions

View File

@@ -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());

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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));
}
};
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -33,4 +33,6 @@ public interface IndexInfo {
boolean exists(String indexName);
Map<String, String> getConfig(String name);
boolean isExpired();
}

View File

@@ -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;
}
}

View File

@@ -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");

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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"));

View File

@@ -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() );

View File

@@ -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));
}
}

View File

@@ -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() {

View File

@@ -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);
}
}
}

View File

@@ -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(