DATAGRAPH-699 - Implement missing operations for Cypher based REST-API separately.

This commit is contained in:
Michael Hunger
2015-08-31 07:52:49 +02:00
parent af4ce06faa
commit 13fce210a2
43 changed files with 866 additions and 468 deletions

View File

@@ -0,0 +1,62 @@
package org.springframework.data.neo4j.aspects;
import org.springframework.data.neo4j.annotation.*;
import org.springframework.data.neo4j.support.index.IndexType;
import java.util.Date;
import java.util.UUID;
/**
* @author mh
* @since 30.08.15
*/
@RelationshipEntity(type="LEAD")
public class LeadRelationship {
@GraphId
private Long id;
@Indexed(unique = true)
private Long uuid;
@Indexed(indexType = IndexType.SIMPLE, indexName = "date-index")
private Date createdDate = new Date();
@StartNode
private Person person;
@EndNode
private Group group;
public LeadRelationship() {
}
public LeadRelationship(Person person, Group group) {
this.person = person;
this.group = group;
this.uuid = Math.abs(UUID.randomUUID().getMostSignificantBits());
}
public Long getId() {
return id;
}
public Long getUuid() {
return uuid;
}
public Date getCreatedDate() {
return createdDate;
}
public Person getPerson() {
return person;
}
public Group getGroup() {
return group;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
}

View File

@@ -121,6 +121,7 @@
<value>org.springframework.data.neo4j.aspects.Developer</value>
<value>org.springframework.data.neo4j.aspects.Person</value>
<value>org.springframework.data.neo4j.aspects.Group</value>
<value>org.springframework.data.neo4j.aspects.LeadRelationship</value>
<value>org.springframework.data.neo4j.aspects.SubGroup</value>
<value>org.springframework.data.neo4j.aspects.Toyota</value>
<value>org.springframework.data.neo4j.aspects.Volvo</value>

View File

@@ -27,23 +27,30 @@ import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.index.impl.lucene.AbstractIndexHits;
import org.neo4j.rest.graphdb.converter.RestEntityExtractor;
import org.neo4j.rest.graphdb.converter.RestIndexHitsConverter;
import org.neo4j.rest.graphdb.entity.RestEntity;
import org.neo4j.rest.graphdb.entity.RestEntityCache;
import org.neo4j.rest.graphdb.entity.RestNode;
import org.neo4j.rest.graphdb.entity.RestRelationship;
import org.neo4j.rest.graphdb.index.IndexInfo;
import org.neo4j.rest.graphdb.index.RestIndex;
import org.neo4j.rest.graphdb.index.RestIndexManager;
import org.neo4j.rest.graphdb.index.SimpleIndexHits;
import org.neo4j.rest.graphdb.query.*;
import org.neo4j.rest.graphdb.transaction.TransactionFinishListener;
import org.neo4j.rest.graphdb.traversal.RestTraversalDescription;
import org.neo4j.rest.graphdb.traversal.RestTraverser;
import org.neo4j.rest.graphdb.util.QueryResult;
import org.neo4j.rest.graphdb.util.QueryResultBuilder;
import org.neo4j.rest.graphdb.util.ResultConverter;
import org.springframework.dao.DataIntegrityViolationException;
import javax.ws.rs.core.Response;
import java.util.*;
import static java.util.Arrays.asList;
import static org.neo4j.helpers.collection.MapUtil.map;
import static org.neo4j.rest.graphdb.RestAPIIndexImpl.queryPath;
import static org.neo4j.rest.graphdb.query.CypherTransaction.ResultType.row;
import static org.neo4j.rest.graphdb.query.CypherTransaction.Statement;
@@ -64,8 +71,9 @@ public class RestAPICypherImpl implements RestAPI {
public static final String GET_REL_TYPES_QUERY = _MATCH_NODE_QUERY + " MATCH (n)-[r]-() RETURN distinct type(r) as relType";
private RestIndexManager restIndex = new RestIndexManager(this);
private RestIndexManager restIndexOld;
private RestAPIIndex restAPIIndex;
private final RestEntityCache entityCache = new RestEntityCache(this);
private RestEntityExtractor restEntityExtractor = new RestEntityExtractor(this);
private String createNodeQuery(Collection<String> labels) {
String labelString = toLabelString(labels);
@@ -98,7 +106,7 @@ public class RestAPICypherImpl implements RestAPI {
protected RestAPICypherImpl(RestAPI restAPI) {
this.restAPI = restAPI;
restIndexOld = new RestIndexManager(restAPI);
restAPIIndex = new RestAPIIndexImpl(this);
}
@Override
@@ -117,8 +125,41 @@ public class RestAPICypherImpl implements RestAPI {
}
@Override
public RestRelationship addToCache(RestRelationship restRelationship) {
return restAPI.addToCache(restRelationship);
public RestNode addToCache(RestNode restNode) {
return entityCache.addToCache(restNode);
}
@Override
public RestRelationship addToCache(RestRelationship rel) {
return entityCache.addToCache(rel);
}
@Override
public RestNode getNodeFromCache(long id) {
return entityCache.getNode(id);
}
@Override
public RestRelationship getRelFromCache(long id) {
return entityCache.getRelationship(id);
}
@Override
public void removeNodeFromCache(long id) {
entityCache.removeNode(id);
}
@Override
public void removeRelFromCache(long id) {
entityCache.removeRelationship(id);
}
@Override
public RestNode getNodeById(long id) {
return getNodeById(id, Load.FromServer);
}
@Override
public RestRelationship getRelationshipById(long id) {
return getRelationshipById(id, Load.FromServer);
}
@Override
@@ -145,33 +186,6 @@ public class RestAPICypherImpl implements RestAPI {
}
}
public RestNode getNodeFromCache(long id) {
return restAPI.getNodeFromCache(id);
}
public RestRelationship getRelFromCache(long id) {
return restAPI.getRelFromCache(id);
}
@Override
public void removeNodeFromCache(long id) {
restAPI.removeNodeFromCache(id);
}
@Override
public void removeRelFromCache(long id) {
restAPI.removeRelFromCache(id);
}
@Override
public RestNode getNodeById(long id) {
return getNodeById(id, Load.FromServer);
}
@Override
public RestRelationship getRelationshipById(long id) {
return getRelationshipById(id, Load.FromServer);
}
private RestNode toNode(List<Object> row) {
long id = ((Number) row.get(0)).longValue();
List<String> labels = (List<String>) row.get(1);
@@ -216,10 +230,6 @@ public class RestAPICypherImpl implements RestAPI {
return addToCache(toNode(result.next()));
}
public RestNode addToCache(RestNode restNode) {
return restAPI.addToCache(restNode);
}
@Override
public RestRelationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<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;
@@ -315,6 +325,7 @@ public class RestAPICypherImpl implements RestAPI {
@Override
public void addLabels(RestNode node, Collection<String> labels) {
if (labels==null || labels.isEmpty()) return;
String statement = _MATCH_NODE_QUERY + " SET n" + toLabelString(labels) + _QUERY_RETURN_NODE;
CypherTransaction.Result result = runQuery(statement, map("id", node.getId()));
@@ -336,9 +347,31 @@ public class RestAPICypherImpl implements RestAPI {
return txManager;
}
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> IndexHits<S> getIndexQuery(Class<S> entityType, String indexName, String key, Object value) {
String indexPath = RestAPIIndexImpl.indexPath(entityType, indexName, key, value);
RequestResult response = getRestRequest().get(indexPath);
if (response.statusIs(Response.Status.OK)) {
return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response);
} else {
return new SimpleIndexHits<S>(Collections.emptyList(), 0, entityType, this);
}
}
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> IndexHits<S> queryIndexQuery(Class<S> entityType, String indexName, String key, Object value) {
String indexPath = queryPath(entityType, indexName, key, value);
RequestResult response = getRestRequest().get(indexPath);
if (response.statusIs(Response.Status.OK)) {
return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response);
} else {
return new SimpleIndexHits<S>(Collections.emptyList(), 0, entityType, this);
}
}
@Override
public <S extends PropertyContainer> IndexHits<S> getIndex(Class<S> entityType, String indexName, String key, Object value) {
if (value instanceof Query) return restAPI.getIndex(entityType, indexName, key, value);
if (value instanceof Query) return getIndexQuery(entityType, indexName, key, value);
String index = key == null ? ":`" + indexName + "`({query})" : ":`" + indexName + "`(`" + key + "`={query})";
if (Node.class.isAssignableFrom(entityType)) {
String statement = "start n=node" + index + _QUERY_RETURN_NODE;
@@ -355,7 +388,7 @@ public class RestAPICypherImpl implements RestAPI {
@Override
public <S extends PropertyContainer> IndexHits<S> queryIndex(Class<S> entityType, String indexName, String key, Object value) {
if (value instanceof Query) return restAPI.queryIndex(entityType,indexName,key,value);
if (value instanceof Query) return queryIndexQuery(entityType, indexName, key, value);
String index = ":`" + indexName + "`({query})";
if (key != null && !key.isEmpty() && !value.toString().contains(":")) value = key + ":"+value;
if (Node.class.isAssignableFrom(entityType)) {
@@ -395,7 +428,7 @@ public class RestAPICypherImpl implements RestAPI {
@Override
public RestIndexManager index() {
return restIndex;
return restAPIIndex.index();
}
@@ -403,10 +436,10 @@ public class RestAPICypherImpl implements RestAPI {
public void deleteEntity(RestEntity entity) {
if (entity instanceof Node) {
runQuery(_MATCH_NODE_QUERY + " DELETE n", map("id", entity.getId()));
restAPI.removeNodeFromCache(entity.getId());
removeNodeFromCache(entity.getId());
} else if (entity instanceof Relationship) {
runQuery(_MATCH_REL_QUERY + " DELETE r", map("id", entity.getId()));
restAPI.removeRelFromCache(entity.getId());
removeRelFromCache(entity.getId());
}
}
@@ -441,13 +474,13 @@ public class RestAPICypherImpl implements RestAPI {
// todo handle within cypher tx
@Override
public RestNode getOrCreateNode(RestIndex<Node> index, String key, Object value, final Map<String, Object> properties, Collection<String> labels) {
return restAPI.getOrCreateNode(index, key, value, properties, labels);
return restAPIIndex.getOrCreateNode(index, key, value, properties, labels);
}
// todo handle within cypher tx
@Override
public RestRelationship getOrCreateRelationship(RestIndex<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 restAPIIndex.getOrCreateRelationship(index, key, value, start, end, type, properties);
}
public CypherResult query(String statement, Map<String, Object> params) {
@@ -530,9 +563,15 @@ public class RestAPICypherImpl implements RestAPI {
}
}
private static final String FULLPATH = "fullpath";
@Override
public RestTraverser traverse(RestNode restNode, Map<String, Object> description) {
return restAPI.traverse(restNode, description);
final RequestResult result = getRestRequest().with(restNode.getUri()).post("traverse/" + FULLPATH, description);
if (result.statusOtherThan(Response.Status.OK)) throw new RuntimeException(String.format("Error executing traversal: %d %s",result.getStatus(), description));
final Object col = result.toEntity();
if (!(col instanceof Collection)) throw new RuntimeException(String.format("Unexpected traversal result, %s instead of collection", col != null ? col.getClass() : null));
return new RestTraverser((Collection) col,restNode.getRestApi());
}
public RequestResult batch(Collection<Map<String, Object>> batchRequestData) {
@@ -551,25 +590,13 @@ public class RestAPICypherImpl implements RestAPI {
@Override
@SuppressWarnings("unchecked")
public void createIndex(String type, String indexName, Map<String, String> config) {
restAPI.createIndex(type, indexName, config);
restAPIIndex.createIndex(type, indexName, config);
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> RestIndex<T> createIndex(Class<T> type, String indexName, Map<String, String> config) {
resetIndex(type);
if (Node.class.isAssignableFrom(type)) {
return (RestIndex<T>) index().forNodes( indexName, config);
}
if (Relationship.class.isAssignableFrom(type)) {
return (RestIndex<T>) index().forRelationships(indexName, config);
}
throw new IllegalArgumentException("Required Node or Relationship types to create index, got " + type);
}
@Override
public void resetIndex(Class type) {
restAPI.resetIndex(type);
return restAPIIndex.createIndex(type,indexName,config);
}
@Override
@@ -634,59 +661,92 @@ public class RestAPICypherImpl implements RestAPI {
@Override
public boolean isAutoIndexingEnabled(Class<? extends PropertyContainer> clazz) {
return restAPI.isAutoIndexingEnabled(clazz);
return restAPIIndex.isAutoIndexingEnabled(clazz);
}
@Override
public void setAutoIndexingEnabled(Class<? extends PropertyContainer> clazz, boolean enabled) {
restAPI.setAutoIndexingEnabled(clazz, enabled);
restAPIIndex.setAutoIndexingEnabled(clazz, enabled);
}
@Override
public Set<String> getAutoIndexedProperties(Class forClass) {
return restAPI.getAutoIndexedProperties(forClass);
return restAPIIndex.getAutoIndexedProperties(forClass);
}
@Override
public void startAutoIndexingProperty(Class forClass, String s) {
restAPI.startAutoIndexingProperty(forClass, s);
restAPIIndex.startAutoIndexingProperty(forClass, s);
}
@Override
public void stopAutoIndexingProperty(Class forClass, String s) {
restAPI.stopAutoIndexingProperty(forClass, s);
restAPIIndex.stopAutoIndexingProperty(forClass, s);
}
@Override
public void delete(RestIndex index) {
restAPI.delete(index);
restAPIIndex.delete(index);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key, Object value) {
restAPI.removeFromIndex(index, entity, key, value);
restAPIIndex.removeFromIndex(index, entity, key, value);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key) {
restAPI.removeFromIndex(index, entity, key);
restAPIIndex.removeFromIndex(index, entity, key);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity) {
restAPI.removeFromIndex(index, entity);
restAPIIndex.removeFromIndex(index, entity);
}
@Override
public <T extends PropertyContainer> void addToIndex(T entity, RestIndex index, String key, Object value) {
restAPI.addToIndex(entity, index, key, value);
public <T extends PropertyContainer> void addToIndex(final T entity, final RestIndex index, final String key, final Object value) {
if (!getTxManager().isActive()) {
restAPIIndex.addToIndex(entity, index, key, value);
return;
}
getTxManager().getRemoteCypherTransaction().registerListener(new TransactionFinishListener() {
@Override
public void comitted() {
restAPIIndex.addToIndex(entity, index, key, value);
}
@Override
public void rolledBack() {
}
});
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> T putIfAbsent(T entity, RestIndex index, String key, Object value) {
return restAPI.putIfAbsent(entity, index, key, value);
public <T extends PropertyContainer> T putIfAbsent(final T entity, final RestIndex index, final String key, final Object value) {
if (!getTxManager().isActive()) {
return restAPIIndex.putIfAbsent(entity, index, key, value);
}
getTxManager().getRemoteCypherTransaction().registerListener(new TransactionFinishListener() {
@Override
public void comitted() {
T result = restAPIIndex.putIfAbsent(entity, index, key, value);
if (result == null || result.equals(entity)) return;
throw new DataIntegrityViolationException("Unique property "+key+" was to be set to duplicate value "+value);
}
@Override
public void rolledBack() {
}
});
return entity;
}
@Override
public IndexInfo indexInfo(final String indexType) {
return restAPIIndex.indexInfo(indexType);
}
@Override
@@ -694,10 +754,6 @@ public class RestAPICypherImpl implements RestAPI {
return restAPI.hasToUpdate(lastUpdate);
}
@Override
public IndexInfo indexInfo(final String indexType) {
return restAPI.indexInfo(indexType);
}
@Override
@@ -721,14 +777,37 @@ public class RestAPICypherImpl implements RestAPI {
@Override
public RestEntityExtractor getEntityExtractor() {
return restAPI.getEntityExtractor();
return restEntityExtractor;
}
@Override
public RestEntity createRestEntity(Map data) {
return restAPI.createRestEntity(data);
if (data.containsKey("id") && data.containsKey("properties")) {
long id = asLong(data, "id");
Map props = (Map) data.get("properties");
if (data.containsKey("type")) {
return RestRelationship.fromCypher(id,(String)data.get("type"),props,asLong(data, "startNode"),asLong(data, "endNode"),this);
}
if (data.containsKey("labels")) {
List<String> labels = (List<String>) data.get("labels");
return entityCache.addToCache(RestNode.fromCypher(id,labels,props, this));
}
}
final String uri = (String) data.get("self");
if (uri == null || uri.isEmpty()) return null;
if (uri.contains("/node/")) {
return entityCache.addToCache(new RestNode(data, this));
}
if (uri.contains("/relationship/")) {
return new RestRelationship(data, this);
}
return null;
}
protected long asLong(Map data, String idKey) {
Object idValue = data.get(idKey);
return idValue instanceof Number ? ((Number)idValue).longValue() : Long.parseLong(idValue.toString());
}
public Iterable<Node> getAllNodes() {
String statement = "MATCH (n) " + _QUERY_RETURN_NODE;

View File

@@ -23,20 +23,16 @@ import org.neo4j.graphdb.*;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.helpers.collection.IterableWrapper;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.index.lucene.ValueContext;
import org.neo4j.rest.graphdb.entity.RestEntityCache;
import org.neo4j.rest.graphdb.query.CypherRestResult;
import org.neo4j.rest.graphdb.converter.RelationshipIterableConverter;
import org.neo4j.rest.graphdb.converter.RestEntityExtractor;
import org.neo4j.rest.graphdb.converter.RestIndexHitsConverter;
import org.neo4j.rest.graphdb.entity.RestEntity;
import org.neo4j.rest.graphdb.entity.RestEntityCache;
import org.neo4j.rest.graphdb.entity.RestNode;
import org.neo4j.rest.graphdb.entity.RestRelationship;
import org.neo4j.rest.graphdb.index.IndexInfo;
import org.neo4j.rest.graphdb.index.RestIndex;
import org.neo4j.rest.graphdb.index.RestIndexManager;
import org.neo4j.rest.graphdb.index.RetrievedIndexInfo;
import org.neo4j.rest.graphdb.index.SimpleIndexHits;
import org.neo4j.rest.graphdb.query.CypherRestResult;
import org.neo4j.rest.graphdb.query.CypherResult;
import org.neo4j.rest.graphdb.query.RestQueryResult;
import org.neo4j.rest.graphdb.transaction.NullTransaction;
@@ -44,19 +40,14 @@ import org.neo4j.rest.graphdb.traversal.RestDirection;
import org.neo4j.rest.graphdb.traversal.RestTraversal;
import org.neo4j.rest.graphdb.traversal.RestTraversalDescription;
import org.neo4j.rest.graphdb.traversal.RestTraverser;
import org.neo4j.rest.graphdb.util.JsonHelper;
import org.neo4j.rest.graphdb.util.QueryResult;
import org.neo4j.rest.graphdb.util.ResultConverter;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static java.lang.String.format;
import static javax.ws.rs.core.Response.Status.CREATED;
import static org.neo4j.helpers.collection.MapUtil.map;
import static org.neo4j.rest.graphdb.ExecutingRestRequest.encode;
@@ -72,15 +63,16 @@ public class RestAPIImpl implements RestAPI {
private long entityRefetchTimeInMillis = TimeUnit.SECONDS.toMillis(1000); //TODO move to cache
private final RestEntityCache entityCache = new RestEntityCache(this);
private RestEntityExtractor restEntityExtractor = new RestEntityExtractor(this);
private RestIndexManager restIndexManager = new RestIndexManager(this);
private Map<String,IndexInfo> indexInfos = new HashMap<>();
private final RestAPIIndexImpl restIndexAPI;
public RestAPIImpl(String uri) {
this.restRequest = createRestRequest(uri, null, null);
restIndexAPI = new RestAPIIndexImpl(this);
}
public RestAPIImpl(String uri, String user, String password) {
this.restRequest = createRestRequest(uri, user, password);
restIndexAPI = new RestAPIIndexImpl(this);
}
protected RestRequest createRestRequest(String uri, String user, String password) {
@@ -89,7 +81,7 @@ public class RestAPIImpl implements RestAPI {
@Override
public RestIndexManager index() {
return restIndexManager;
return restIndexAPI.index();
}
@Override
@@ -191,20 +183,6 @@ public class RestAPIImpl implements RestAPI {
return entityCache.addToCache(node);
}
@Override
public RestNode getOrCreateNode(RestIndex<Node> index, String key, Object value, final Map<String, Object> properties, Collection<String> labels) {
if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null");
final Map<String, Object> data = map("key", key, "value", value, "properties", properties);
final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data);
if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) {
RestNode node = (RestNode) getEntityExtractor().convertFromRepresentation(result);
addLabels(node, labels);
node.setLabels(labels);
return entityCache.addToCache(node);
}
throw new RuntimeException(String.format("Error retrieving or creating node for key %s and value %s with index %s", key, value, index.getIndexName()));
}
private String toLabelString(String[] labels) {
if (labels==null || labels.length == 0) return "";
StringBuilder sb = new StringBuilder();
@@ -215,7 +193,7 @@ public class RestAPIImpl implements RestAPI {
}
private RestNode createRestNode(RequestResult result) {
public RestNode createRestNode(RequestResult result) {
if (result.statusIs(Status.NOT_FOUND)) {
throw new NotFoundException("Node not found");
}
@@ -229,6 +207,16 @@ public class RestAPIImpl implements RestAPI {
return entityCache.addToCache(node);
}
public RestRelationship createRestRelationship(RequestResult requestResult, PropertyContainer element) {
if (requestResult.statusOtherThan(CREATED)) {
final int status = requestResult.getStatus();
throw new RuntimeException("Error creating relationship " + status+" "+requestResult.getText());
}
final String location = requestResult.getLocation();
if (requestResult.isMap()) return new RestRelationship(requestResult.toMap(), this);
return new RestRelationship(location, this);
}
@Override
public RestRelationship createRelationship(Node startNode, Node endNode, RelationshipType type, Map<String, Object> props) {
// final RestRequest restRequest = ((RestNode) startNode).getRestRequest();
@@ -242,58 +230,6 @@ public class RestAPIImpl implements RestAPI {
return createRestRelationship(requestResult, startNode);
}
private RestRelationship createRestRelationship(RequestResult requestResult, PropertyContainer element) {
if (requestResult.statusOtherThan(CREATED)) {
final int status = requestResult.getStatus();
throw new RuntimeException("Error creating relationship " + status+" "+requestResult.getText());
}
final String location = requestResult.getLocation();
if (requestResult.isMap()) return new RestRelationship(requestResult.toMap(), this);
return new RestRelationship(location, this);
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> RestIndex<T> getIndex(String indexName) {
final RestIndexManager index = this.index();
if (index.existsForNodes(indexName)) return (RestIndex<T>) index.forNodes(indexName);
if (index.existsForRelationships(indexName)) return (RestIndex<T>) index.forRelationships(indexName);
throw new IllegalArgumentException("Index " + indexName + " does not yet exist");
}
@Override
@SuppressWarnings("unchecked")
public void createIndex(String type, String indexName, Map<String, String> config) {
Map<String,Object> data=new HashMap<String, Object>();
data.put("name",indexName);
data.put("config",config);
restRequest.post("index/" + type, data);
IndexInfo indexInfo = indexInfos.get(type);
if (indexInfo!=null) indexInfo.setExpired();
}
public void resetIndex(Class type) {
if (Node.class.isAssignableFrom(type)) {
indexInfo(RestIndexManager.NODE).setExpired();
}
if (Relationship.class.isAssignableFrom(type)) {
indexInfo(RestIndexManager.RELATIONSHIP).setExpired();
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> RestIndex<T> createIndex(Class<T> type, String indexName, Map<String, String> config) {
resetIndex(type);
if (Node.class.isAssignableFrom(type)) {
return (RestIndex<T>) index().forNodes( indexName, config);
}
if (Relationship.class.isAssignableFrom(type)) {
return (RestIndex<T>) index().forRelationships(indexName, config);
}
throw new IllegalArgumentException("Required Node or Relationship types to create index, got " + type);
}
@Override
public void close() {
ExecutingRestRequest.shutdown();
@@ -360,56 +296,6 @@ public class RestAPIImpl implements RestAPI {
return "MERGE (n:`"+labelName+"` {`"+key+"`: {value}}) ON CREATE SET n={props} "+setLabels+ _QUERY_RETURN_NODE;
}
@Override
public boolean isAutoIndexingEnabled(Class<? extends PropertyContainer> clazz) {
RequestResult response = getRestRequest().get(buildPathAutoIndexerStatus(clazz));
if (response.statusIs(Response.Status.OK)) {
return Boolean.parseBoolean(response.getText());
} else {
throw new IllegalStateException("received " + response);
}
}
@Override
public void setAutoIndexingEnabled(Class<? extends PropertyContainer> clazz, boolean enabled) {
RequestResult response = getRestRequest().put(buildPathAutoIndexerStatus(clazz), enabled);
if (response.statusOtherThan(Status.NO_CONTENT)) {
throw new IllegalStateException("received " + response);
}
}
@Override
public Set<String> getAutoIndexedProperties(Class forClass) {
RequestResult response = getRestRequest().get(buildPathAutoIndexerProperties(forClass).toString());
Collection<String> autoIndexedProperties = (Collection<String>) JsonHelper.readJson(response.getText());
return new HashSet<String>(autoIndexedProperties);
}
@Override
public void startAutoIndexingProperty(Class forClass, String s) {
try {
// we need to use a inputstream instead of the string directly. Otherwise "post" implicitly uses
// StreamJsonHelper.writeJsonTo which quotes a given string
InputStream stream = new ByteArrayInputStream(s.getBytes("UTF-8"));
RequestResult response = getRestRequest().post(buildPathAutoIndexerProperties(forClass).toString(), stream);
if (response.statusOtherThan(Status.NO_CONTENT)) {
throw new IllegalStateException("received " + response);
}
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
@Override
public void stopAutoIndexingProperty(Class forClass, String s) {
RequestResult response = getRestRequest().delete(buildPathAutoIndexerProperties(forClass).append("/").append(s).toString());
if (response.statusOtherThan(Status.NO_CONTENT)) {
throw new IllegalStateException("received " + response);
}
}
@Override
public void removeLabel(RestNode node, String label) {
RequestResult response = getRestRequest().with(node.getUri()).delete("labels/" + encode(label));
@@ -513,23 +399,6 @@ public class RestAPIImpl implements RestAPI {
}
}
private String buildPathAutoIndexerStatus(Class<? extends PropertyContainer> clazz) {
return buildPathAutoIndexerBase(clazz).append("/status").toString();
}
private StringBuilder buildPathAutoIndexerProperties(Class<? extends PropertyContainer> clazz) {
return buildPathAutoIndexerBase(clazz).append("/properties");
}
private StringBuilder buildPathAutoIndexerBase(Class<? extends PropertyContainer> clazz) {
return new StringBuilder().append("index/auto/").append(indexTypeName(clazz));
}
public RestRequest getRestRequest() {
return restRequest;
}
@Override
public RestTraversalDescription createTraversalDescription() {
@@ -564,63 +433,12 @@ public class RestAPIImpl implements RestAPI {
}
public String indexPath( Class entityType, String indexName, String key, Object value ) {
String typeName = indexTypeName(entityType);
return "index/" + typeName + "/" + encode(indexName) + (key!=null? "/" + encode(key) :"") + (value!=null ? "/" + encode(value):"");
}
private String indexTypeName(Class entityType) {
return entityType.getSimpleName().toLowerCase();
}
public String indexPath( Class entityType, String indexName ) {
return "index/" + indexTypeName(entityType) + "/" + encode(indexName);
}
private String queryPath( Class entityType, String indexName, String key, Object value ) {
return indexPath( entityType, indexName, key,null) + "?query="+ encode(value);
}
@Override
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> IndexHits<S> getIndex(Class<S> entityType, String indexName, String key, Object value) {
String indexPath = indexPath(entityType, indexName, key, value);
RequestResult response = restRequest.get(indexPath);
if (response.statusIs(Response.Status.OK)) {
return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response);
} else {
return new SimpleIndexHits<S>(Collections.emptyList(), 0, entityType, this);
}
}
@Override
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> IndexHits<S> queryIndex(Class<S> entityType, String indexName, String key, Object value) {
String indexPath = queryPath(entityType, indexName, key, value);
RequestResult response = restRequest.get(indexPath);
if (response.statusIs(Response.Status.OK)) {
return new RestIndexHitsConverter(this, entityType).convertFromRepresentation(response);
} else {
return new SimpleIndexHits<S>(Collections.emptyList(), 0, entityType, this);
}
}
@Override
public void deleteEntity(RestEntity entity) {
getRestRequest().with(entity.getUri()).delete( "" );
entityCache.removeNode(entity.getId());
}
@Override
public IndexInfo indexInfo(final String indexType) {
IndexInfo indexInfo = indexInfos.get(indexType);
if (indexInfo != null && !indexInfo.isExpired()) {
return indexInfo;
}
RequestResult response = restRequest.get("index/" + encode(indexType));
indexInfo = new RetrievedIndexInfo(response);
indexInfos.put(indexType,indexInfo);
return indexInfo;
}
@Override
public void setPropertyOnEntity(RestEntity entity, String key, Object value) {
RequestResult result = getRestRequest().with(entity.getUri()).put("properties/" + encode(key), value);
@@ -649,81 +467,6 @@ public class RestAPIImpl implements RestAPI {
return properties;
}
private void deleteIndex(String indexPath) {
getRestRequest().delete(indexPath);
}
@Override
public void delete(RestIndex index) {
deleteIndex(indexPath(index, null, null));
resetIndex(index.getEntityType());
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key, Object value) {
String indexPath = indexPath(index, key, value);
deleteIndex(indexPath(indexPath, entity));
resetIndex(index.getEntityType());
}
protected <T extends PropertyContainer> String indexPath(String indexPath, T restEntity) {
return indexPath + "/" + ((RestEntity)restEntity).getId();
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key) {
String indexPath = indexPath(index, key, null);
deleteIndex(indexPath(indexPath, entity));
resetIndex(index.getEntityType());
}
private String indexPath(RestIndex index, String key, Object value) {
return indexPath(index.getEntityType(), index.getIndexName(), key, value);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity) {
deleteIndex(indexPath(indexPath(index, null, null), entity));
resetIndex(index.getEntityType());
}
public String uniqueIndexPath(RestIndex index) {
return indexPath(index,null,null) + "?uniqueness=get_or_create";
}
@Override
public <T extends PropertyContainer> void addToIndex(T entity, RestIndex index, String key, Object value) {
final RestEntity restEntity = (RestEntity) entity;
String uri = restEntity.getUri();
if (value instanceof ValueContext) {
value = ((ValueContext)value).getCorrectValue();
}
final Map<String, Object> data = map("key", key, "value", value, "uri", uri);
final RequestResult result = getRestRequest().post(indexPath(index, null, null), data);
if (result.statusOtherThan(Status.CREATED)) throw new RuntimeException(String.format("Error adding element %d %s %s to index %s", restEntity.getId(), key, value, index.getIndexName()));
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> T putIfAbsent(T entity, RestIndex index, String key, Object value) {
final RestEntity restEntity = (RestEntity) entity;
restEntity.flush();
String uri = restEntity.getUri();
if (value instanceof ValueContext) {
value = ((ValueContext)value).getCorrectValue();
}
final Map<String, Object> data = map("key", key, "value", value, "uri", uri);
final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data);
if (result.statusIs(Response.Status.CREATED)) {
if (index.getEntityType().equals(Node.class)) return (T)createRestNode(result);
if (index.getEntityType().equals(Relationship.class)) return (T)createRestRelationship(result,restEntity);
}
if (result.statusIs(Response.Status.OK)) {
return (T) getEntityExtractor().convertFromRepresentation(result);
}
throw new RuntimeException(String.format("Error adding element %d %s %s to index %s", restEntity.getId(), key, value, index.getIndexName()));
}
@Override
public boolean hasToUpdate(long lastUpdate) {
return timeElapsed(lastUpdate, getEntityRefetchTimeInMillis());
@@ -738,18 +481,6 @@ public class RestAPIImpl implements RestAPI {
return System.currentTimeMillis() - since > isItGreaterThanThis;
}
@Override
public RestRelationship getOrCreateRelationship(RestIndex<Relationship> index, String key, Object value, final RestNode start, final RestNode end, final String type, final Map<String, Object> properties) {
if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null");
if (start == null || end == null || type == null) throw new IllegalArgumentException("Neither start, end nore type must be null");
final Map<String, Object> data = map("key", key, "value", value, "properties", properties, "start", start.getUri(), "end", end.getUri(), "type", type);
final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data);
if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) {
return (RestRelationship) getEntityExtractor().convertFromRepresentation(result);
}
throw new RuntimeException(String.format("Error retrieving or creating relationship for key %s and value %s with index %s", key, value, index.getIndexName()));
}
public org.neo4j.rest.graphdb.query.CypherResult query(String statement, Map<String, Object> params) {
params = (params==null) ? Collections.<String,Object>emptyMap() : params;
final RequestResult requestResult = getRestRequest().post("cypher", map("query", statement, "params", params));
@@ -822,6 +553,11 @@ public class RestAPIImpl implements RestAPI {
return null;
}
@Override
public RestRequest getRestRequest() {
return restRequest;
}
protected long asLong(Map data, String idKey) {
Object idValue = data.get(idKey);
return idValue instanceof Number ? ((Number)idValue).longValue() : Long.parseLong(idValue.toString());
@@ -830,4 +566,100 @@ public class RestAPIImpl implements RestAPI {
public RequestResult batch(Collection<Map<String, Object>> batchRequestData) {
return restRequest.post("batch",batchRequestData);
}
@Override
public IndexInfo indexInfo(String indexType) {
return restIndexAPI.indexInfo(indexType);
}
@Override
public void stopAutoIndexingProperty(Class forClass, String s) {
restIndexAPI.stopAutoIndexingProperty(forClass, s);
}
@Override
public void startAutoIndexingProperty(Class forClass, String s) {
restIndexAPI.startAutoIndexingProperty(forClass, s);
}
@Override
public Set<String> getAutoIndexedProperties(Class forClass) {
return restIndexAPI.getAutoIndexedProperties(forClass);
}
@Override
public void setAutoIndexingEnabled(Class<? extends PropertyContainer> clazz, boolean enabled) {
restIndexAPI.setAutoIndexingEnabled(clazz, enabled);
}
@Override
public boolean isAutoIndexingEnabled(Class<? extends PropertyContainer> clazz) {
return restIndexAPI.isAutoIndexingEnabled(clazz);
}
@Override
public <T extends PropertyContainer> RestIndex<T> createIndex(Class<T> type, String indexName, Map<String, String> config) {
return restIndexAPI.createIndex(type, indexName, config);
}
@Override
public void createIndex(String type, String indexName, Map<String, String> config) {
restIndexAPI.createIndex(type, indexName, config);
}
@Override
public <T extends PropertyContainer> RestIndex<T> getIndex(String indexName) {
return restIndexAPI.getIndex(indexName);
}
@Override
public RestRelationship getOrCreateRelationship(RestIndex<Relationship> index, String key, Object value, RestNode start, RestNode end, String type, Map<String, Object> properties) {
return restIndexAPI.getOrCreateRelationship(index, key, value, start, end, type, properties);
}
@Override
public RestNode getOrCreateNode(RestIndex<Node> index, String key, Object value, Map<String, Object> properties, Collection<String> labels) {
return restIndexAPI.getOrCreateNode(index, key, value, properties, labels);
}
@Override
public <T extends PropertyContainer> T putIfAbsent(T entity, RestIndex index, String key, Object value) {
return restIndexAPI.putIfAbsent(entity, index, key, value);
}
@Override
public <T extends PropertyContainer> void addToIndex(T entity, RestIndex index, String key, Object value) {
restIndexAPI.addToIndex(entity, index, key, value);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity) {
restIndexAPI.removeFromIndex(index, entity);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key) {
restIndexAPI.removeFromIndex(index, entity, key);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key, Object value) {
restIndexAPI.removeFromIndex(index, entity, key, value);
}
@Override
public void delete(RestIndex index) {
restIndexAPI.delete(index);
}
@Override
public <S extends PropertyContainer> IndexHits<S> queryIndex(Class<S> entityType, String indexName, String key, Object value) {
return restIndexAPI.queryIndex(entityType, indexName, key, value);
}
@Override
public <S extends PropertyContainer> IndexHits<S> getIndex(Class<S> entityType, String indexName, String key, Object value) {
return restIndexAPI.getIndex(entityType, indexName, key, value);
}
}

View File

@@ -0,0 +1,332 @@
package org.neo4j.rest.graphdb;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.index.lucene.ValueContext;
import org.neo4j.rest.graphdb.converter.RestIndexHitsConverter;
import org.neo4j.rest.graphdb.entity.RestEntity;
import org.neo4j.rest.graphdb.entity.RestNode;
import org.neo4j.rest.graphdb.entity.RestRelationship;
import org.neo4j.rest.graphdb.index.*;
import org.neo4j.rest.graphdb.util.JsonHelper;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.*;
import static javax.ws.rs.core.Response.Status.CREATED;
import static org.neo4j.helpers.collection.MapUtil.map;
import static org.neo4j.rest.graphdb.ExecutingRestRequest.encode;
/**
* @author mh
* @since 25.08.15
*/
public class RestAPIIndexImpl implements RestAPIIndex {
private final RestRequest restRequest;
private RestIndexManager restIndexManager;
private Map<String,IndexInfo> indexInfos = new HashMap<>();
private final RestAPI restAPI;
public RestAPIIndexImpl(RestAPI restAPI) {
this.restAPI = restAPI;
restRequest = restAPI.getRestRequest();
restIndexManager = new RestIndexManager(restAPI);
}
@Override
public RestIndexManager index() {
return restIndexManager;
}
public static String indexPath( Class entityType, String indexName ) {
return "index/" + indexTypeName(entityType) + "/" + encode(indexName);
}
public static String queryPath( Class entityType, String indexName, String key, Object value ) {
return indexPath( entityType, indexName, key,null) + "?query="+ encode(value);
}
public static String indexPath( Class entityType, String indexName, String key, Object value ) {
String typeName = indexTypeName(entityType);
return "index/" + typeName + "/" + encode(indexName) + (key!=null? "/" + encode(key) :"") + (value!=null ? "/" + encode(value):"");
}
public static String indexTypeName(Class entityType) {
return entityType.getSimpleName().toLowerCase();
}
@Override
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> IndexHits<S> getIndex(Class<S> entityType, String indexName, String key, Object value) {
String indexPath = indexPath(entityType, indexName, key, value);
RequestResult response = restRequest.get(indexPath);
if (response.statusIs(Response.Status.OK)) {
return new RestIndexHitsConverter(restAPI, entityType).convertFromRepresentation(response);
} else {
return new SimpleIndexHits<S>(Collections.emptyList(), 0, entityType, restAPI);
}
}
@Override
@SuppressWarnings("unchecked")
public <S extends PropertyContainer> IndexHits<S> queryIndex(Class<S> entityType, String indexName, String key, Object value) {
String indexPath = queryPath(entityType, indexName, key, value);
RequestResult response = restRequest.get(indexPath);
if (response.statusIs(Response.Status.OK)) {
return new RestIndexHitsConverter(restAPI, entityType).convertFromRepresentation(response);
} else {
return new SimpleIndexHits<S>(Collections.emptyList(), 0, entityType, restAPI);
}
}
private void deleteIndex(String indexPath) {
getRestRequest().delete(indexPath);
}
@Override
public void delete(RestIndex index) {
deleteIndex(indexPath(index, null, null));
resetIndex(index.getEntityType());
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key, Object value) {
String indexPath = indexPath(index, key, value);
deleteIndex(indexPath(indexPath, entity));
resetIndex(index.getEntityType());
}
protected <T extends PropertyContainer> String indexPath(String indexPath, T restEntity) {
return indexPath + "/" + ((RestEntity)restEntity).getId();
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity, String key) {
String indexPath = indexPath(index, key, null);
deleteIndex(indexPath(indexPath, entity));
resetIndex(index.getEntityType());
}
private String indexPath(RestIndex index, String key, Object value) {
return indexPath(index.getEntityType(), index.getIndexName(), key, value);
}
@Override
public <T extends PropertyContainer> void removeFromIndex(RestIndex index, T entity) {
deleteIndex(indexPath(indexPath(index, null, null), entity));
resetIndex(index.getEntityType());
}
public String uniqueIndexPath(RestIndex index) {
return indexPath(index,null,null) + "?uniqueness=get_or_create";
}
@Override
public <T extends PropertyContainer> void addToIndex(T entity, RestIndex index, String key, Object value) {
final RestEntity restEntity = (RestEntity) entity;
String uri = restEntity.getUri();
if (value instanceof ValueContext) {
value = ((ValueContext)value).getCorrectValue();
}
final Map<String, Object> data = map("key", key, "value", value, "uri", uri);
final RequestResult result = getRestRequest().post(indexPath(index, null, null), data);
if (result.statusOtherThan(Response.Status.CREATED)) {
throw new RuntimeException(String.format("Error adding element %d %s %s to index %s status %s\n%s", restEntity.getId(), key, value, index.getIndexName(), result.getStatus(),result.getText()));
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> T putIfAbsent(T entity, RestIndex index, String key, Object value) {
final RestEntity restEntity = (RestEntity) entity;
restEntity.flush();
String uri = restEntity.getUri();
if (value instanceof ValueContext) {
value = ((ValueContext)value).getCorrectValue();
}
final Map<String, Object> data = map("key", key, "value", value, "uri", uri);
final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data);
if (result.statusIs(Response.Status.CREATED)) {
if (index.getEntityType().equals(Node.class)) return (T)createRestNode(result);
if (index.getEntityType().equals(Relationship.class)) return (T)createRestRelationship(result, restEntity);
}
if (result.statusIs(Response.Status.OK)) {
return (T) restAPI.getEntityExtractor().convertFromRepresentation(result);
}
throw new RuntimeException(String.format("Error adding element %d %s %s to index %s", restEntity.getId(), key, value, index.getIndexName()));
}
public RestNode createRestNode(RequestResult result) {
if (result.statusIs(Response.Status.NOT_FOUND)) {
throw new NotFoundException("Node not found");
}
RestNode node = null;
if (result.statusIs(CREATED)) {
node = result.isMap() ? new RestNode(result.toMap(), restAPI) : new RestNode(result.getLocation(), restAPI);
}
if (node == null && result.statusIs(Response.Status.OK)) {
node = new RestNode(result.toMap(), restAPI);
}
return restAPI.addToCache(node);
}
public RestRelationship createRestRelationship(RequestResult requestResult, PropertyContainer element) {
if (requestResult.statusOtherThan(CREATED)) {
final int status = requestResult.getStatus();
throw new RuntimeException("Error creating relationship " + status+" "+requestResult.getText());
}
final String location = requestResult.getLocation();
if (requestResult.isMap()) return new RestRelationship(requestResult.toMap(), restAPI);
return new RestRelationship(location, restAPI);
}
@Override
public RestNode getOrCreateNode(RestIndex<Node> index, String key, Object value, final Map<String, Object> properties, Collection<String> labels) {
if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null");
final Map<String, Object> data = map("key", key, "value", value, "properties", properties);
final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data);
if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) {
RestNode node = (RestNode) restAPI.getEntityExtractor().convertFromRepresentation(result);
restAPI.addLabels(node, labels);
node.setLabels(labels);
return restAPI.addToCache(node);
}
throw new RuntimeException(String.format("Error retrieving or creating node for key %s and value %s with index %s", key, value, index.getIndexName()));
}
@Override
public RestRelationship getOrCreateRelationship(RestIndex<Relationship> index, String key, Object value, final RestNode start, final RestNode end, final String type, final Map<String, Object> properties) {
if (index==null || key == null || value==null) throw new IllegalArgumentException("Unique index "+index+" key "+key+" value must not be null");
if (start == null || end == null || type == null) throw new IllegalArgumentException("Neither start, end nore type must be null");
final Map<String, Object> data = map("key", key, "value", value, "properties", properties, "start", start.getUri(), "end", end.getUri(), "type", type);
final RequestResult result = getRestRequest().post(uniqueIndexPath(index), data);
if (result.statusIs(Response.Status.CREATED) || result.statusIs(Response.Status.OK)) {
return (RestRelationship) restAPI.getEntityExtractor().convertFromRepresentation(result);
}
throw new RuntimeException(String.format("Error retrieving or creating relationship for key %s and value %s with index %s", key, value, index.getIndexName()));
}
private String buildPathAutoIndexerStatus(Class<? extends PropertyContainer> clazz) {
return buildPathAutoIndexerBase(clazz).append("/status").toString();
}
private StringBuilder buildPathAutoIndexerProperties(Class<? extends PropertyContainer> clazz) {
return buildPathAutoIndexerBase(clazz).append("/properties");
}
private StringBuilder buildPathAutoIndexerBase(Class<? extends PropertyContainer> clazz) {
return new StringBuilder().append("index/auto/").append(indexTypeName(clazz));
}
public RestRequest getRestRequest() {
return restRequest;
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> RestIndex<T> getIndex(String indexName) {
final RestIndexManager index = this.index();
if (index.existsForNodes(indexName)) return (RestIndex<T>) index.forNodes(indexName);
if (index.existsForRelationships(indexName)) return (RestIndex<T>) index.forRelationships(indexName);
throw new IllegalArgumentException("Index " + indexName + " does not yet exist");
}
@Override
@SuppressWarnings("unchecked")
public void createIndex(String type, String indexName, Map<String, String> config) {
Map<String,Object> data=new HashMap<String, Object>();
data.put("name",indexName);
data.put("config",config);
restRequest.post("index/" + type, data);
IndexInfo indexInfo = indexInfos.get(type);
if (indexInfo!=null) indexInfo.setExpired();
}
public void resetIndex(Class type) {
if (Node.class.isAssignableFrom(type)) {
indexInfo(RestIndexManager.NODE).setExpired();
}
if (Relationship.class.isAssignableFrom(type)) {
indexInfo(RestIndexManager.RELATIONSHIP).setExpired();
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends PropertyContainer> RestIndex<T> createIndex(Class<T> type, String indexName, Map<String, String> config) {
resetIndex(type);
if (Node.class.isAssignableFrom(type)) {
return (RestIndex<T>) index().forNodes( indexName, config);
}
if (Relationship.class.isAssignableFrom(type)) {
return (RestIndex<T>) index().forRelationships(indexName, config);
}
throw new IllegalArgumentException("Required Node or Relationship types to create index, got " + type);
}
@Override
public boolean isAutoIndexingEnabled(Class<? extends PropertyContainer> clazz) {
RequestResult response = getRestRequest().get(buildPathAutoIndexerStatus(clazz));
if (response.statusIs(Response.Status.OK)) {
return Boolean.parseBoolean(response.getText());
} else {
throw new IllegalStateException("received " + response);
}
}
@Override
public void setAutoIndexingEnabled(Class<? extends PropertyContainer> clazz, boolean enabled) {
RequestResult response = getRestRequest().put(buildPathAutoIndexerStatus(clazz), enabled);
if (response.statusOtherThan(Response.Status.NO_CONTENT)) {
throw new IllegalStateException("received " + response);
}
}
@Override
public Set<String> getAutoIndexedProperties(Class forClass) {
RequestResult response = getRestRequest().get(buildPathAutoIndexerProperties(forClass).toString());
Collection<String> autoIndexedProperties = (Collection<String>) JsonHelper.readJson(response.getText());
return new HashSet<String>(autoIndexedProperties);
}
@Override
public void startAutoIndexingProperty(Class forClass, String s) {
try {
// we need to use a inputstream instead of the string directly. Otherwise "post" implicitly uses
// StreamJsonHelper.writeJsonTo which quotes a given string
InputStream stream = new ByteArrayInputStream(s.getBytes("UTF-8"));
RequestResult response = getRestRequest().post(buildPathAutoIndexerProperties(forClass).toString(), stream);
if (response.statusOtherThan(Response.Status.NO_CONTENT)) {
throw new IllegalStateException("received " + response);
}
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
@Override
public void stopAutoIndexingProperty(Class forClass, String s) {
RequestResult response = getRestRequest().delete(buildPathAutoIndexerProperties(forClass).append("/").append(s).toString());
if (response.statusOtherThan(Response.Status.NO_CONTENT)) {
throw new IllegalStateException("received " + response);
}
}
@Override
public IndexInfo indexInfo(final String indexType) {
IndexInfo indexInfo = indexInfos.get(indexType);
if (indexInfo != null && !indexInfo.isExpired()) {
return indexInfo;
}
RequestResult response = restRequest.get("index/" + encode(indexType));
indexInfo = new RetrievedIndexInfo(response);
indexInfos.put(indexType,indexInfo);
return indexInfo;
}
}

View File

@@ -25,8 +25,6 @@ public interface RestAPIInternal {
// todo add to cache or update data in cache
RestEntity createRestEntity(Map data);
void resetIndex(Class type);
public enum Load {
FromCache,
FromServer,

View File

@@ -69,6 +69,13 @@ public abstract class RestEntity implements PropertyContainer, UpdatableRestResu
this.id = id;
setProperties((Map<String, Object>) structuralData.get("data"));
}
public RestEntity(RestEntity entity, RestAPI facade) {
this.restApi = facade;
this.structuralData = entity.structuralData;
this.id = entity.id;
this.uri = nodeUri(facade,id);
setProperties((Map<String, Object>) structuralData.get("data"));
}
public static String nodeUri(RestAPI facade, long id) {
return facade.getBaseUri()+"/node/" + id;

View File

@@ -58,6 +58,10 @@ public class RestNode extends RestEntity implements Node {
super(id,restData,facade);
setLabels(labels);
}
public RestNode(RestNode node, RestAPI facade) {
super(node,facade);
setLabels(node.labels);
}
public static RestNode fromCypher(long id, Collection<String> labels, Map<String, Object> props, RestAPI facade) {
Map<String, Object> restData = map("data", props, "self", RestNode.nodeUri(facade, id));//,"metadata",map("id",String.valueOf("id"),"labels",labels)
@@ -73,7 +77,7 @@ public class RestNode extends RestEntity implements Node {
public void updateFrom(RestEntity entity, RestAPI restApi) {
super.updateFrom(entity, restApi);
RestNode node = (RestNode) entity;
if (node.lastLabelFetchTime > 0 && node.labels != null) {
if (node.lastLabelFetchTime > lastLabelFetchTime && node.labels != null) {
setLabels(node.labels);
}
}

View File

@@ -47,6 +47,9 @@ public class RestRelationship extends RestEntity implements Relationship {
public RestRelationship( Map<?, ?> data, RestAPI restApi ) {
super( data, restApi );
}
public RestRelationship( RestRelationship relationship, RestAPI restApi ) {
super( relationship, restApi );
}
@Override
protected void doUpdate() {

View File

@@ -28,6 +28,8 @@ import org.neo4j.graphdb.index.*;
import org.neo4j.index.impl.lucene.LuceneIndexImplementation;
import org.neo4j.rest.graphdb.RestAPI;
import static org.neo4j.rest.graphdb.ExecutingRestRequest.encode;
public class RestIndexManager implements IndexManager {
public static final String RELATIONSHIP = "relationship";
public static final String NODE = "node";

View File

@@ -24,12 +24,17 @@ import org.neo4j.rest.graphdb.query.CypherTransaction;
import javax.transaction.Status;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static org.neo4j.helpers.collection.MapUtil.map;
public class RemoteCypherTransaction implements Transaction {
private final List<TransactionFinishListener> listeners = new ArrayList<>();
int status = Status.STATUS_NO_TRANSACTION;
boolean success, failure;
CypherTransaction tx;
@@ -46,6 +51,12 @@ public class RemoteCypherTransaction implements Transaction {
'}';
}
public void registerListener(TransactionFinishListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
public RemoteCypherTransaction(CypherTransaction tx) {
this.tx = tx;
status = Status.STATUS_ACTIVE;
@@ -68,10 +79,6 @@ public class RemoteCypherTransaction implements Transaction {
close();
}
public void terminate() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
if (tx() != null && innerCounter.decrementAndGet() > 0) {
@@ -83,16 +90,25 @@ public class RemoteCypherTransaction implements Transaction {
status = Status.STATUS_COMMITTED;
}
else {
tx().rollback();
if (tx()!=null) tx().rollback();
status = Status.STATUS_ROLLEDBACK;
}
} finally {
tx = null;
notifyFinish();
}
}
private void notifyFinish() {
for (TransactionFinishListener listener : listeners) {
if (status == Status.STATUS_COMMITTED) listener.comitted();
else listener.rolledBack();
}
listeners.clear();
}
private CypherTransaction tx() {
if (tx == null) throw new IllegalStateException("No transaction active");
if (tx == null && !failure) throw new IllegalStateException("No transaction active");
return tx;
}
@@ -129,4 +145,9 @@ public class RemoteCypherTransaction implements Transaction {
public boolean isActive() {
return tx != null;
}
public void terminate() {
failure();
close();
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2002-2013 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.rest.graphdb.transaction;
/**
* @author mh
* @since 20.05.15
*/
public interface TransactionFinishListener {
void comitted();
void rolledBack();
}

View File

@@ -32,7 +32,6 @@ import org.neo4j.rest.graphdb.query.CypherTransaction;
public class QueryResultBuilder<T> implements QueryResult<T> {
private CypherTransaction.Result cypherResult;
private Iterable<T> result;
private final ResultConverter defaultConverter;
private final boolean isClosableIterable;
@@ -44,18 +43,10 @@ public class QueryResultBuilder<T> implements QueryResult<T> {
public QueryResultBuilder(Iterable<T> result, final ResultConverter<T,?> defaultConverter) {
this.result = result;
this.isClosableIterable = result instanceof IndexHits || result instanceof ClosableIterable;
this.isClosableIterable = result instanceof IndexHits || result instanceof ClosableIterable || result instanceof AutoCloseable;;
this.defaultConverter = defaultConverter;
}
public static String replaceParams(String statement, Map<String, Object> params) {
if (params==null || params.isEmpty()) return statement;
for (Map.Entry<String, Object> param : params.entrySet()) {
statement = statement.replaceAll("%"+param.getKey()+"\\b",""+param.getValue());
}
return statement;
}
@Override
public <R> ConvertedResult<R> to(Class<R> type) {
return this.to(type, defaultConverter);
@@ -121,9 +112,15 @@ public class QueryResultBuilder<T> implements QueryResult<T> {
private void closeIfNeeded() {
if (isClosableIterable && !isClosed) {
if (result instanceof IndexHits) {
((IndexHits) result).close();
((IndexHits) result).close();
} else if (result instanceof ClosableIterable) {
((ClosableIterable) result).close();
((ClosableIterable) result).close();
} else if (result instanceof AutoCloseable) {
try {
((AutoCloseable)result).close();
} catch (Exception e) {
// ignore
}
}
isClosed=true;
}

View File

@@ -32,9 +32,12 @@ import org.neo4j.rest.graphdb.MatrixDataGraph.RelTypes;
import org.neo4j.test.ImpermanentGraphDatabase;
import org.neo4j.test.TestGraphDatabaseFactory;
import java.util.HashSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.neo4j.helpers.collection.IteratorUtil.addToCollection;
import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
/**
@@ -149,7 +152,7 @@ public class MatrixDatabaseTest {
Index<Node> goodGuys = index.forNodes("heroes");
IndexHits<Node> hits = goodGuys.query( "name", "*" );
Traverser heroesTraverser = getHeroes();
assertEquals( heroesTraverser.nodes().iterator().next().getId(), hits.iterator().next().getId() );
assertEquals( addToCollection(heroesTraverser.nodes(), new HashSet<Node>()), addToCollection(hits.iterator() , new HashSet<Node>()));
}

View File

@@ -28,6 +28,7 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Transaction;
import org.neo4j.helpers.collection.IteratorUtil;
@@ -40,7 +41,11 @@ public class RestCypherQueryEngineTest extends RestTestBase {
private RestAPI restAPI;
private MatrixDataGraph embeddedMatrixdata;
private MatrixDataGraph restMatrixData;
protected GraphDatabaseService createRestGraphDatabase() {
return new RestGraphDatabase(SERVER_ROOT_URI);
}
@Before
public void init() throws Exception {
embeddedMatrixdata = new MatrixDataGraph(getGraphDatabase(),nodeId()).createNodespace();

View File

@@ -28,6 +28,7 @@ import org.apache.lucene.index.Term;
import org.apache.lucene.search.TermQuery;
import org.junit.Assert;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.index.Index;
@@ -43,6 +44,10 @@ public class RestIndexTest extends RestTestBase {
private static final String NODE_INDEX_NAME = "NODE_INDEX";
private static final String REL_INDEX_NAME = "REL_INDEX";
protected GraphDatabaseService createRestGraphDatabase() {
return new RestGraphDatabase(SERVER_ROOT_URI);
}
@Test
public void testAddToNodeIndex() {
nodeIndex().add(node(), "name", "test");

View File

@@ -1,6 +1,8 @@
package org.neo4j.rest.graphdb;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException;
/**
* @author mh
@@ -13,4 +15,9 @@ public class UpdateRelationshipRestApiImplTest extends UpdateRelationshipTest {
restAPI = new RestAPIImpl(SERVER_ROOT_URI);
return new RestGraphDatabase(restAPI);
}
@Test
public void testUpdateRelationshipsRemoveAddNoType() throws Exception {
super.testUpdateRelationshipsRemoveAddNoType();
}
}

View File

@@ -67,6 +67,13 @@ public class UpdateRelationshipTest extends RestTestBase {
updateRelationships();
}
/*
given
(remove)-[:LIKES]->(node)
(node)-[:KNOWS]->(keep)
when update to (node)-->(keep)
then
*/
@Test(expected = CypherTransactionExecutionException.class)
public void testUpdateRelationshipsRemoveAddNoType() throws Exception {
Node node = node();
@@ -74,6 +81,7 @@ public class UpdateRelationshipTest extends RestTestBase {
node.createRelationshipTo(keep, KNOWS);
type = null;
updateTo = asList(keep);
expected = asList(remove, keep);
updateRelationships();
}

View File

@@ -16,19 +16,13 @@
package org.springframework.data.neo4j.rest.integration;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.data.neo4j.aspects.support.EntityPropertyValidationTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
@@ -37,7 +31,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestEntityPropertyValidationTests extends EntityPropertyValidationTests {
@BeforeClass

View File

@@ -19,15 +19,14 @@ package org.springframework.data.neo4j.rest.integration;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.data.neo4j.aspects.support.FinderTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author mh
@@ -36,7 +35,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestFinderTests extends FinderTests {
@BeforeClass
@@ -54,4 +52,9 @@ public class RestFinderTests extends FinderTests {
RestTestBase.shutdownDb();
}
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testFindRelationshipEntity() {
super.testFindRelationshipEntity();
}
}

View File

@@ -23,13 +23,9 @@ import org.junit.runner.RunWith;
import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException;
import org.springframework.data.neo4j.aspects.support.GraphRepositoryTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
@@ -38,7 +34,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestGraphRepositoryTests extends GraphRepositoryTests {
@BeforeClass

View File

@@ -25,12 +25,8 @@ import org.springframework.data.neo4j.aspects.Person;
import org.springframework.data.neo4j.aspects.support.IndexTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
@@ -43,7 +39,7 @@ import static org.springframework.data.neo4j.aspects.Person.persistedPerson;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
"classpath:RestTests-context-index.xml"})
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class RestIndexTests extends IndexTests {

View File

@@ -24,12 +24,16 @@ import org.junit.Ignore;
import org.junit.Test;
import org.neo4j.graphdb.GraphDatabaseService;
import org.springframework.dao.DataAccessException;
import org.springframework.data.neo4j.config.JtaTransactionManagerFactoryBean;
import org.springframework.data.neo4j.config.NullTransactionManager;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.rest.support.RestTestHelper;
import org.springframework.data.neo4j.template.Neo4jTemplateApiTests;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.jta.UserTransactionAdapter;
import javax.transaction.TransactionManager;
public class RestNeo4jTemplateApiTests extends Neo4jTemplateApiTests
{
@@ -64,7 +68,8 @@ public class RestNeo4jTemplateApiTests extends Neo4jTemplateApiTests
@Override
protected PlatformTransactionManager createTransactionManager()
{
return new JtaTransactionManager(new NullTransactionManager());
TransactionManager txm = graphDatabase.getTransactionManager();
return new JtaTransactionManager(new UserTransactionAdapter( txm ), txm);
}
@Override

View File

@@ -31,12 +31,8 @@ import org.springframework.data.neo4j.aspects.Mentorship;
import org.springframework.data.neo4j.aspects.Person;
import org.springframework.data.neo4j.aspects.support.NodeEntityRelationshipTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
@@ -49,7 +45,6 @@ import static org.junit.Assert.assertFalse;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestNodeEntityRelationshipTests extends NodeEntityRelationshipTests {
@BeforeClass

View File

@@ -22,12 +22,8 @@ import org.neo4j.graphdb.ConstraintViolationException;
import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException;
import org.springframework.data.neo4j.aspects.support.NodeEntityTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
@@ -35,8 +31,7 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
"classpath:RestTests-context-index.xml"})
public class RestNodeEntityTests extends NodeEntityTests {
@BeforeClass
@@ -60,7 +55,7 @@ public class RestNodeEntityTests extends NodeEntityTests {
// super.testSetShortProperty();
}
@Test(expected = CypherTransactionExecutionException.class)
@Test(expected = IllegalStateException.class)
public void testDefaultFailOnDuplicateSetToTrueCausesExceptionWhenAnotherDuplicateEntityCreated() {
super.testDefaultFailOnDuplicateSetToTrueCausesExceptionWhenAnotherDuplicateEntityCreated();
}

View File

@@ -22,12 +22,8 @@ import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.data.neo4j.aspects.support.ProjectionTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
@@ -36,7 +32,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestProjectionTests extends ProjectionTests {
@BeforeClass

View File

@@ -22,12 +22,8 @@ import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.data.neo4j.aspects.support.PropertyTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
@@ -36,7 +32,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestPropertyTests extends PropertyTests {
@BeforeClass

View File

@@ -16,27 +16,30 @@
package org.springframework.data.neo4j.rest.integration;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.data.neo4j.aspects.LeadRelationship;
import org.springframework.data.neo4j.aspects.support.RelationshipEntityTests;
import org.springframework.data.neo4j.aspects.Group;
import org.springframework.data.neo4j.aspects.Person;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.junit.Assert.assertEquals;
/**
* @author mh
* @since 28.03.11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestRelationshipEntityTests extends RelationshipEntityTests {
@BeforeClass
@@ -55,4 +58,21 @@ public class RestRelationshipEntityTests extends RelationshipEntityTests {
}
@Test
@Transactional
public void testSaveRelationship() throws Exception {
Person person = personRepository.save(new Person("Michael", 39));
Group group = groupRepository.save(new Group());
LeadRelationship rel = new LeadRelationship(person,group);
LeadRelationship saved = neo4jTemplate.save(rel);
LeadRelationship loaded = neo4jTemplate.findOne(saved.getId(),LeadRelationship.class);
assertEquals(saved.getUuid(),loaded.getUuid());
saved.setCreatedDate(new Date());
LeadRelationship saved2 = neo4jTemplate.save(saved);
LeadRelationship loaded2 = neo4jTemplate.findOne(saved2.getId(),LeadRelationship.class);
assertEquals(saved.getId(),loaded2.getId());
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy HH:mm");
assertEquals(format.format(saved2.getCreatedDate()),format.format(loaded2.getCreatedDate()));
}
}

View File

@@ -33,12 +33,8 @@ import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase;
import org.springframework.data.neo4j.rest.SpringRestGraphDatabase;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import static org.junit.Assert.assertEquals;
@@ -49,7 +45,6 @@ import static org.junit.Assert.assertEquals;
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RestRelationshipTests.MyConfig.class)
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestRelationshipTests {
@Configuration

View File

@@ -22,12 +22,8 @@ import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.data.neo4j.aspects.support.TraversalTests;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
@@ -36,7 +32,6 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"} )
@TestExecutionListeners( {CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class} )
public class RestTraversalTests extends TraversalTests
{

View File

@@ -22,12 +22,10 @@ import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.neo4j.rest.support.RestTestBase;
import org.springframework.data.neo4j.unique.common.CommonUniqueNumericIdClub;
import org.springframework.data.neo4j.unique.legacy.UniqueLegacyIndexBasedEntityTests;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.assertEquals;
@@ -39,7 +37,6 @@ import static org.junit.Assert.assertEquals;
@ContextConfiguration(locations = {
"classpath:unique-legacy-test-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestUniqueEntityTests extends UniqueLegacyIndexBasedEntityTests {
@BeforeClass
@@ -58,9 +55,9 @@ public class RestUniqueEntityTests extends UniqueLegacyIndexBasedEntityTests {
}
@Override
@Ignore
@Test
public void shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity() {
super.shouldOnlyCreateSingleInstanceForUniqueNumericNodeEntity();
// needs transaction separation due to legacy index endpoints not participating in transactions
}
}

View File

@@ -22,6 +22,7 @@ import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.Relationship;
import org.neo4j.rest.graphdb.query.CypherTransactionExecutionException;
import java.util.Arrays;
@@ -73,7 +74,7 @@ public class RestEntityTests extends RestTestBase {
assertEquals(null, restGraphDatabase.getNodeById(nodeId));
}
@Test(expected = NotFoundException.class)
@Test(expected = RuntimeException.class)
public void testRemoveRelationship() {
Node refNode = createNode();
Node node = createNode();

View File

@@ -21,18 +21,16 @@ import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.rest.graphdb.RestGraphDatabase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.aspects.support.query.QueryEngineTests;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase;
import org.springframework.data.neo4j.rest.SpringCypherRestGraphDatabase;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.data.neo4j.rest.SpringRestGraphDatabase;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
@@ -40,12 +38,11 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:RestTests-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
"classpath:RestTests-context-index.xml"})
public class RestQueryEngineTests extends QueryEngineTests {
@Autowired
SpringCypherRestGraphDatabase restGraphDatabase;
SpringRestGraphDatabase restGraphDatabase;
@BeforeClass
public static void startDb() throws Exception {

View File

@@ -57,6 +57,7 @@ public class RestTestBase {
db = new ImpermanentGraphDatabase();
final ServerConfigurator configurator = new ServerConfigurator(db);
configurator.configuration().setProperty(Configurator.WEBSERVER_PORT_PROPERTY_KEY,PORT);
configurator.configuration().setProperty("dbms.security.auth_enabled",false);
final WrappingNeoServerBootstrapper bootstrapper = new WrappingNeoServerBootstrapper(db, configurator);
int exit = bootstrapper.start();
if (exit != 0 ) throw new IllegalStateException("Server not started correctly.");

View File

@@ -41,6 +41,7 @@ public class RestTestHelper
db = new ImpermanentGraphDatabase();
final ServerConfigurator configurator = new ServerConfigurator(db);
configurator.configuration().setProperty(Configurator.WEBSERVER_PORT_PROPERTY_KEY,PORT);
configurator.configuration().setProperty("dbms.security.auth_enabled",false);
final WrappingNeoServerBootstrapper bootstrapper = new WrappingNeoServerBootstrapper(db, configurator);
bootstrapper.start();
neoServer = bootstrapper.getServer();

View File

@@ -74,6 +74,7 @@ public class SpringPluginInitializerTests extends SpringPluginInitializer implem
};
final Configuration configuration = configurator.configuration();
configuration.setProperty(Configurator.WEBSERVER_PORT_PROPERTY_KEY, PORT);
configuration.setProperty("dbms.security.auth_enabled", false);
final WrappingNeoServerBootstrapper bootstrapper = new WrappingNeoServerBootstrapper(db, configurator);
touched=0;
bootstrapper.start();

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="graphDatabaseService" class="org.springframework.data.neo4j.rest.SpringRestGraphDatabase" destroy-method="shutdown">
<constructor-arg index="0" value="http://localhost:7470/db/data" />
</bean>
<alias name="graphDatabaseService" alias="graphDatabase"/>
</beans>

View File

@@ -1,2 +1,4 @@
org.neo4j.server.database.location=target/test-db
org.neo4j.server.thirdparty_jaxrs_classes=org.springframework.data.neo4j.rest.support=/test
dbms.security.auth_enabled=false

View File

@@ -1 +1,2 @@
org.neo4j.server.database.location=target/test-db
dbms.security.auth_enabled=false

View File

@@ -25,6 +25,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.neo4j.mapping.MappingPolicy;
import java.io.Closeable;
import java.util.*;
/**
@@ -124,6 +125,12 @@ public class QueryResultBuilder<T> implements Result<T> {
((IndexHits) result).close();
} else if (result instanceof ClosableIterable) {
((ClosableIterable) result).close();
} else if (result instanceof AutoCloseable) {
try {
((AutoCloseable)result).close();
} catch (Exception e) {
// ignore
}
}
isClosed=true;
}

View File

@@ -147,6 +147,8 @@ public class Neo4jTemplateApiTests {
@Test
public void testIndexNode() throws Exception {
template.index("node", node1, "name","node1");
transaction.success();transaction.close();
transaction = graphDatabase.beginTx();
Index<Node> index = graphDatabase.getIndex("node");
Node lookedUpNode= index.get( "name", "node1" ).getSingle();
assertThat("same node from index", lookedUpNode, is(node1));

View File

@@ -38,6 +38,7 @@ import org.springframework.data.neo4j.unique.legacy.repository.UniqueClubReposit
import org.springframework.data.neo4j.unique.legacy.repository.UniqueNumericIdClubRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

View File

@@ -178,7 +178,7 @@ Users of Spring Data Neo4j have two ways of very concisely configuring it. Eithe
=== XML namespace
The XML namespace can be used to configure Spring Data Neo4j. The `config` element provides an XML-based configuration of Spring Data Neo4j in one line. It has four attributes. * `base-package` points to a set of packages (provided as a comma separated String of names) which SDN will scan for locate all of your domain entity classes (`@NodeEntity` and `@RelationshipEntity`). NOTE: Neo4j 2.0 introduced the requirement to separately manage schema and data transactions which altered some options for SDN with regards be being able to automatically detect and register `@NodeEntity` and `@RelationshipEntity`s on the fly. Several approaches were attempted to try and handle this automatically with SDN 3.0.X, none of which worked in a satisfactory manner. This has resulted in the base-package becoming a mandatory field now with entity metadata handling becoming an explicit step in the lifecycle.
The XML namespace can be used to configure Spring Data Neo4j. The `config` element provides an XML-based configuration of Spring Data Neo4j in one line. It has four attributes. * `base-package` points to a set of packages (provided as a comma separated String of names) which SDN will scan for locate all of your domain entity classes (`@NodeEntity` and `@RelationshipEntity`). NOTE: Neo4j 2.0 introduced the requirement to separately manage schema and data transactions which altered some options for SDN with regards be being able to automatically detect and register `@NodeEntity` and `@RelationshipEntity` on the fly. Several approaches were attempted to try and handle this automatically with SDN 3.0.X, none of which worked in a satisfactory manner. This has resulted in the base-package becoming a mandatory field now with entity metadata handling becoming an explicit step in the lifecycle.
* `graphDatabaseService` points out the Neo4j instance to use.
* `storeDirectory` is a convenient alternative (instead of `graphDatabaseService`) to point to a directory where a new `EmbeddedGraphDatabase` will be created.
* `entityManagerFactory` is only required for cross-store configuration.