Added Gremlin Plugin, Refactored QueryEngine, QueryOperations

Cypher and Gremlin queries can now take parameters
This commit is contained in:
Michael Hunger
2011-07-25 12:09:46 +02:00
parent db3c61dbf4
commit 85b05507d5
26 changed files with 283 additions and 349 deletions

View File

@@ -187,6 +187,12 @@
</dependency>
<dependency>
<groupId>com.tinkerpop.blueprints</groupId>
<artifactId>blueprints-core</artifactId>
<version>${blueprints.version}</version>
<scope>optional</scope>
</dependency>
<dependency>
<groupId>com.tinkerpop.blueprints</groupId>
<artifactId>blueprints-neo4j-graph</artifactId>

View File

@@ -58,6 +58,13 @@
<groupId>org.neo4j.server.plugin</groupId>
<artifactId>neo4j-cypher-plugin</artifactId>
<version>${neo4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j.server.plugin</groupId>
<artifactId>neo4j-gremlin-plugin</artifactId>
<version>${neo4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.neo4j.app</groupId>

View File

@@ -44,7 +44,7 @@ public class JsonHelper {
return (List<Map<String, Object>>) readJson( json );
}
private static Object readJson( String json ) {
public static Object readJson( String json ) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readValue( json, Object.class );

View File

@@ -26,7 +26,7 @@ import java.util.*;
* @author mh
* @since 22.06.11
*/
public class RestCypherQueryEngine implements QueryEngine {
public class RestCypherQueryEngine implements QueryEngine<Map<String,Object>> {
private final RestRequest restRequest;
private final RestGraphDatabase restGraphDatabase;
private final ResultConverter resultConverter;
@@ -41,7 +41,7 @@ public class RestCypherQueryEngine implements QueryEngine {
}
@Override
public QueryResult<Map<String, Object>> query(String statement) {
public QueryResult<Map<String, Object>> query(String statement, Map<String, Object> params) {
return executeStatement(statement);
}
@@ -52,8 +52,6 @@ public class RestCypherQueryEngine implements QueryEngine {
static class RestQueryResult implements QueryResult<Map<String,Object>> {
QueryResultBuilder<Map<String,Object>> result;
private final RestGraphDatabase restGraphDatabase;
@Override
public <R> ConvertedResult<R> to(Class<R> type) {
@@ -76,49 +74,9 @@ public class RestCypherQueryEngine implements QueryEngine {
}
public RestQueryResult(Map<?, ?> result, RestGraphDatabase restGraphDatabase, ResultConverter resultConverter) {
this.restGraphDatabase = restGraphDatabase;
List<String> columns= (List<String>) result.get("columns");
final List<Map<String, Object>> data = extractData(result, columns);
final RestTableResultExtractor extractor = new RestTableResultExtractor(new RestEntityExtractor(restGraphDatabase));
final List<Map<String, Object>> data = extractor.extract(result);
this.result=new QueryResultBuilder<Map<String,Object>>(data, resultConverter);
}
private List<Map<String, Object>> extractData(Map<?, ?> restResult, List<String> columns) {
List<List<?>> rows= (List<List<?>>) restResult.get("data");
List<Map<String,Object>> result=new ArrayList<Map<String, Object>>(rows.size());
for (List<?> row : rows) {
result.add(mapRow(columns,row));
}
return result;
}
private Map<String, Object> mapRow(List<String> columns, List<?> row) {
int columnCount=columns.size();
Map<String,Object> newRow=new HashMap<String, Object>(columnCount);
for (int i = 0; i < columnCount; i++) {
final Object value = row.get(i);
newRow.put(columns.get(i), convertFromRepresentation(value));
}
return newRow;
}
private Object convertFromRepresentation(Object value) {
if (value instanceof Map) {
RestEntity restEntity = createRestEntity((Map) value);
if (restEntity!=null) return restEntity;
}
return value;
}
private RestEntity createRestEntity(Map data) {
final String uri = (String) data.get("self");
if (uri == null || uri.isEmpty()) return null;
if (uri.contains("/node/")) {
return new RestNode(data,restGraphDatabase);
}
if (uri.contains("/relationship/")) {
return new RestRelationship(data,restGraphDatabase);
}
return null;
}
}
}

View File

@@ -23,9 +23,9 @@ import org.neo4j.graphdb.event.TransactionEventHandler;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.Property;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.rest.index.RestIndexManager;
import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter;
import org.springframework.data.neo4j.support.query.QueryEngine;
@@ -105,7 +105,11 @@ public class RestGraphDatabase implements GraphDatabaseService, GraphDatabase {
@Override
public QueryEngine queryEngineFor(QueryEngine.Type type) {
return new RestCypherQueryEngine(this, createResultConverter());
switch (type) {
case Cypher: return new RestCypherQueryEngine(this, createResultConverter());
case Gremlin: return new RestGremlinQueryEngine(this, createResultConverter());
}
throw new IllegalArgumentException("Unknown Query Engine Type "+type);
}
private ResultConverter createResultConverter() {

View File

@@ -132,12 +132,23 @@
<scope>optional</scope>
</dependency>
<dependency>
<groupId>com.tinkerpop.blueprints</groupId>
<artifactId>blueprints-core</artifactId>
<scope>optional</scope>
</dependency>
<dependency>
<groupId>com.tinkerpop.blueprints</groupId>
<artifactId>blueprints-neo4j-graph</artifactId>
<scope>optional</scope>
</dependency>
<dependency>
<groupId>com.tinkerpop</groupId>
<artifactId>gremlin</artifactId>
<scope>optional</scope>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>

View File

@@ -21,6 +21,7 @@ import org.neo4j.helpers.collection.ClosableIterable;
import org.neo4j.helpers.collection.IteratorWrapper;
import java.util.Iterator;
import java.util.Map;
/**
* @author mh
@@ -42,6 +43,14 @@ public class QueryResultBuilder<T> implements QueryResult<T> {
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(),""+param.getValue());
}
return statement;
}
@Override
public <R> ConvertedResult<R> to(Class<R> type) {
return this.to(type, defaultConverter);

View File

@@ -23,7 +23,6 @@ import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.support.query.CypherQueryEngine;
import org.springframework.data.neo4j.support.query.QueryEngine;
@@ -95,7 +94,7 @@ public interface GraphDatabase {
*/
TraversalDescription createTraversalDescription();
QueryEngine queryEngineFor(CypherQueryEngine.Type type);
<T> QueryEngine<T> queryEngineFor(QueryEngine.Type type);
void setConversionService(ConversionService conversionService);
}

View File

@@ -27,7 +27,7 @@ import org.springframework.data.neo4j.core.NodeBacked;
import org.springframework.data.neo4j.core.RelationshipBacked;
import org.springframework.data.neo4j.support.GenericTypeExtractor;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.query.QueryExecutor;
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -235,14 +235,14 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
}
private static class GraphRepositoryQuery implements RepositoryQuery {
private QueryExecutor queryExecutor;
private CypherQueryExecutor queryExecutor;
private final GraphQueryMethod queryMethod;
private final RepositoryMetadata metadata;
private boolean iterableResult;
private Class<?> compoundType;
public GraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final GraphDatabaseContext graphDatabaseContext) {
queryExecutor = new QueryExecutor(graphDatabaseContext);
queryExecutor = new CypherQueryExecutor(graphDatabaseContext);
this.queryMethod = queryMethod;
this.metadata = metadata;
this.iterableResult = Iterable.class.isAssignableFrom(queryMethod.getReturnType());

View File

@@ -27,7 +27,9 @@ import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.Property;
import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter;
import org.springframework.data.neo4j.support.query.CypherQueryEngine;
import org.springframework.data.neo4j.support.query.GremlinQueryEngine;
import org.springframework.data.neo4j.support.query.QueryEngine;
import java.util.Map;
/**
@@ -126,8 +128,12 @@ public class DelegatingGraphDatabase implements GraphDatabase {
}
@Override
public QueryEngine queryEngineFor(CypherQueryEngine.Type type) {
return new CypherQueryEngine(delegate, createResultConverter());
public <T> QueryEngine<T> queryEngineFor(QueryEngine.Type type) {
switch (type) {
case Cypher: return (QueryEngine<T>)new CypherQueryEngine(delegate, createResultConverter());
case Gremlin: return (QueryEngine<T>) new GremlinQueryEngine(delegate);
}
throw new IllegalArgumentException("Unknown Query Engine Type "+type);
}
private ConversionServiceQueryResultConverter createResultConverter() {

View File

@@ -240,8 +240,6 @@ public class GraphDatabaseContext {
return graphDatabaseService.getRelationshipById(id);
}
public GraphDatabaseService getGraphDatabaseService() {
return graphDatabaseService;
}

View File

@@ -42,7 +42,7 @@ import org.springframework.data.neo4j.core.EntityState;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.path.EntityPathPathIterableWrapper;
import org.springframework.data.neo4j.support.query.QueryExecutor;
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
import javax.persistence.Transient;
import javax.persistence.Entity;
@@ -184,17 +184,17 @@ public aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMixinFields<No
return new NodeBackedNodeIterableWrapper<T>(traverser, targetType, Neo4jNodeBacking.aspectOf().graphDatabaseContext);
}
public <T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType) {
final QueryExecutor executor = new QueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
return executor.query(query, targetType);
}
public Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query) {
final QueryExecutor executor = new QueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
return executor.queryForList(query);
}
public <T> T NodeBacked.findByQuery(final String query, final Class<T> targetType) {
final QueryExecutor executor = new QueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
return executor.queryForObject(query, targetType);
}

View File

@@ -29,11 +29,10 @@ import org.springframework.data.neo4j.conversion.ResultConverter;
import java.util.Map;
public class CypherQueryEngine implements QueryEngine, QueryOperations {
public class CypherQueryEngine implements QueryEngine<Map<String,Object>> {
final ExecutionEngine executionEngine;
private ResultConverter resultConverter;
private final DefaultQueryOperations queryOperations;
public CypherQueryEngine(GraphDatabaseService graphDatabaseService) {
this(graphDatabaseService, new DefaultConverter());
@@ -43,34 +42,19 @@ public class CypherQueryEngine implements QueryEngine, QueryOperations {
public CypherQueryEngine(GraphDatabaseService graphDatabaseService, ResultConverter resultConverter) {
this.resultConverter = resultConverter != null ? resultConverter : new DefaultConverter();
this.executionEngine = new ExecutionEngine(graphDatabaseService);
this.queryOperations = new DefaultQueryOperations(this);
}
@Override
public QueryResult<Map<String, Object>> query(String statement) {
public QueryResult<Map<String, Object>> query(String statement, Map<String, Object> params) {
try {
ExecutionResult result = parseAndExecuteQuery(statement);
String parametrizedQuery = QueryResultBuilder.replaceParams(statement,params);
ExecutionResult result = parseAndExecuteQuery(parametrizedQuery);
return new QueryResultBuilder<Map<String,Object>>(result,resultConverter);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, e);
}
}
@Override
public Iterable<Map<String, Object>> queryForList(String statement) {
return queryOperations.queryForList(statement);
}
@Override
public <T> Iterable<T> query(String statement, Class<T> type) {
return queryOperations.query(statement, type);
}
@Override
public <T> T queryForObject(String statement, Class<T> type) {
return queryOperations.queryForObject(statement, type);
}
private ExecutionResult parseAndExecuteQuery(String statement) {
try {
CypherParser parser = new CypherParser();

View File

@@ -19,6 +19,8 @@ package org.springframework.data.neo4j.support.query;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.conversion.EntityResultConverter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
@@ -26,24 +28,31 @@ import java.util.Map;
* @since 10.06.11
* todo limits
*/
public class QueryExecutor implements QueryOperations {
private final QueryEngine queryEngine;
public class CypherQueryExecutor implements QueryOperations<Map<String,Object>> {
private final CypherQueryEngine queryEngine;
public QueryExecutor(GraphDatabaseContext ctx) {
public CypherQueryExecutor(GraphDatabaseContext ctx) {
EntityResultConverter converter = new EntityResultConverter(ctx);
queryEngine = new CypherQueryEngine(ctx.getGraphDatabaseService(), converter);
}
public Iterable<Map<String, Object>> queryForList(String statement) {
return queryEngine.query(statement);
public Iterable<Map<String, Object>> queryForList(String statement, Map<String,Object>...params) {
return queryEngine.query(statement,mergeParams(params));
}
public <T> Iterable<T> query(String statement, Class<T> type) {
return queryEngine.query(statement).to(type);
public <T> Iterable<T> query(String statement, Class<T> type, Map<String,Object>...params) {
return queryEngine.query(statement,mergeParams(params)).to(type);
}
public <T> T queryForObject(String statement, Class<T> type) {
return queryEngine.query(statement).to(type).single();
public <T> T queryForObject(String statement, Class<T> type, Map<String,Object>...params) {
return (T) queryEngine.query(statement,mergeParams(params)).to(type).single();
}
private Map<String,Object> mergeParams(Map<String,Object>...params) {
if (params==null || params.length==0) return Collections.emptyMap();
Map<String,Object> result=new HashMap<String, Object>();
for (Map<String, Object> map : params) {
result.putAll(map);
}
return result;
}
}

View File

@@ -1,44 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.query;
import java.util.Map;
/**
* @author mh
* @since 28.06.11
*/
public class DefaultQueryOperations implements QueryOperations {
private QueryEngine queryEngine;
public DefaultQueryOperations(QueryEngine queryEngine) {
this.queryEngine = queryEngine;
}
public Iterable<Map<String, Object>> queryForList(String statement) {
return queryEngine.query(statement);
}
public <T> Iterable<T> query(String statement, Class<T> type) {
return queryEngine.query(statement).to(type);
}
public <T> T queryForObject(String statement, Class<T> type) {
return queryEngine.query(statement).to(type).single();
}
}

View File

@@ -0,0 +1,108 @@
/**
* 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.support.query;
import com.tinkerpop.blueprints.pgm.Edge;
import com.tinkerpop.blueprints.pgm.Vertex;
import com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jEdge;
import com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jGraph;
import com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jVertex;
import com.tinkerpop.gremlin.pipes.util.Table;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.helpers.collection.IterableWrapper;
import javax.script.*;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@SuppressWarnings("ALL")
public class GremlinExecutor {
public static final int REFRESH_ENGINE_COUNT = 10000;
private final String g = "g";
private volatile ScriptEngine engine;
private ScriptEngine createScriptEngine() {
return new ScriptEngineManager().getEngineByName("gremlin");
}
private static final AtomicInteger executionCount = new AtomicInteger();
private final GraphDatabaseService graphDatabaseService;
public GremlinExecutor(GraphDatabaseService graphDatabaseService) {
this.graphDatabaseService = graphDatabaseService;
}
public Iterable<Object> query(String statement, Map<String,Object> params) {
try {
final Bindings bindings = createBindings(params);
final ScriptEngine engine = engine();
final Object result = engine.eval(statement, bindings);
return getRepresentation(result);
} catch (final ScriptException e) {
throw new RuntimeException("Error executing statement " + statement, e);
}
}
private Bindings createBindings(Map<String, Object> params) {
final Bindings bindings = new SimpleBindings();
bindings.put(g, new Neo4jGraph(graphDatabaseService));
if (params==null) return bindings;
for (Map.Entry<String, Object> entry : params.entrySet()) {
bindings.put(entry.getKey(),entry.getValue());
}
return bindings;
}
private ScriptEngine engine() {
if (engine == null || executionCount.incrementAndGet() > REFRESH_ENGINE_COUNT) {
executionCount.set(0);
this.engine = new ScriptEngineManager().getEngineByName("gremlin");
}
return this.engine;
}
public static Iterable getRepresentation(final Object result) {
if (result instanceof Iterable) {
if (result instanceof Table) {
Table table = (Table) result;
}
return new IterableWrapper((Iterable) result) {
@Override
protected Object underlyingObjectToObject(Object object) {
return getSingleResult(object);
}
};
} else {
return Collections.singleton(getSingleResult(result));
}
}
private static Object getSingleResult(Object result) {
if (result instanceof Vertex) {
return ((Neo4jVertex) result).getRawVertex();
} else if (result instanceof Edge) {
return ((Neo4jEdge) result).getRawEdge();
} else if (result instanceof Neo4jGraph) {
return ((Neo4jGraph) result).getRawGraph();
} else {
return result;
}
}
}

View File

@@ -16,43 +16,19 @@
package org.springframework.data.neo4j.support.query;
import com.tinkerpop.blueprints.pgm.Edge;
import com.tinkerpop.blueprints.pgm.Graph;
import com.tinkerpop.blueprints.pgm.Vertex;
import com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jEdge;
import com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jGraph;
import com.tinkerpop.blueprints.pgm.impls.neo4j.Neo4jVertex;
import com.tinkerpop.gremlin.pipes.util.Table;
import org.neo4j.cypher.commands.Query;
import org.neo4j.cypher.javacompat.CypherParser;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.server.rest.repr.ListRepresentation;
import org.neo4j.server.rest.repr.Representation;
import org.neo4j.server.rest.repr.RepresentationType;
import org.neo4j.server.rest.repr.ValueRepresentation;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.neo4j.conversion.DefaultConverter;
import org.springframework.data.neo4j.conversion.QueryResult;
import org.springframework.data.neo4j.conversion.QueryResultBuilder;
import org.springframework.data.neo4j.conversion.ResultConverter;
import sun.security.provider.certpath.Vertex;
import javax.script.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class GremlinQueryEngine implements QueryEngine {
public class GremlinQueryEngine implements QueryEngine<Object> {
private final String g = "g";
private static final ScriptEngine engine = new ScriptEngineManager().getEngineByName("gremlin");
private ResultConverter resultConverter;
private final DefaultQueryOperations queryOperations;
private final GraphDatabaseService graphDatabaseService;
private final GremlinExecutor gremlinExecutor;
private final ResultConverter resultConverter;
public GremlinQueryEngine(GraphDatabaseService graphDatabaseService) {
this(graphDatabaseService, new DefaultConverter());
@@ -60,110 +36,15 @@ public class GremlinQueryEngine implements QueryEngine {
public GremlinQueryEngine(GraphDatabaseService graphDatabaseService, ResultConverter resultConverter) {
this.graphDatabaseService = graphDatabaseService;
this.resultConverter = resultConverter != null ? resultConverter : new DefaultConverter();
this.queryOperations = new DefaultQueryOperations(this);
this.gremlinExecutor = new GremlinExecutor(graphDatabaseService);
}
@Override
public QueryResult<Map<String, Object>> query(String statement) {
public QueryResult<Object> query(String statement, Map<String, Object> params) {
try {
final Neo4jGraph graph = new Neo4jGraph(graphDatabaseService);
final Bindings bindings = new SimpleBindings();
bindings.put(g, graph);
final Object result = GremlinQueryEngine.engine.eval(statement, bindings);
return getRepresentation(graph, result);
} catch (final ScriptException e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, e);
}
try {
ExecutionResult result = parseAndExecuteQuery(statement);
return new QueryResultBuilder<Map<String, Object>>(result, resultConverter);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, e);
}
}
public static Representation getRepresentation(final Neo4jGraph graph,
final Object result) {
if (result instanceof Iterable) {
RepresentationType type = RepresentationType.STRING;
final List<Representation> results = new ArrayList<Representation>();
if (result instanceof Table) {
type = RepresentationType.STRING;
results.add(new GremlinTableRepresentation((Table) result, graph));
return new ListRepresentation(type, results);
}
for (final Object r : (Iterable) result) {
if (r instanceof Vertex) {
type = RepresentationType.NODE;
results.add(new NodeRepresentation(
((Neo4jVertex) r).getRawVertex()));
} else if (r instanceof Edge) {
type = RepresentationType.RELATIONSHIP;
results.add(new RelationshipRepresentation(
((Neo4jEdge) r).getRawEdge()));
} else if (r instanceof Graph) {
type = RepresentationType.STRING;
results.add(ValueRepresentation.string(graph.getRawGraph().toString()));
} else if (r instanceof Double || r instanceof Float) {
type = RepresentationType.DOUBLE;
results.add(ValueRepresentation.number(((Number) r).doubleValue()));
} else if (r instanceof Long || r instanceof Integer) {
type = RepresentationType.LONG;
results.add(ValueRepresentation.number(((Number) r).longValue()));
} else {
System.out.println("GremlinPlugin: got back" + r);
type = RepresentationType.STRING;
results.add(ValueRepresentation.string(r.toString()));
}
}
return new ListRepresentation(type, results);
} else {
return getSingleResult(graph, result);
}
}
private static Object getSingleResult(Object result) {
if (result instanceof Vertex) {
return ((Neo4jVertex) result).getRawVertex();
} else if (result instanceof Edge) {
return ((Neo4jEdge) result).getRawEdge();
} else if (result instanceof Graph) {
return ValueRepresentation.string(graph.getRawGraph().toString());
} else if (result instanceof Double || result instanceof Float) {
return ValueRepresentation.number(((Number) result).doubleValue());
} else if (result instanceof Long || result instanceof Integer) {
return ValueRepresentation.number(((Number) result).longValue());
} else {
return ValueRepresentation.string(result + "");
}
}
@Override
public Iterable<Map<String, Object>> queryForList(String statement) {
return queryOperations.queryForList(statement);
}
@Override
public <T> Iterable<T> query(String statement, Class<T> type) {
return queryOperations.query(statement, type);
}
@Override
public <T> T queryForObject(String statement, Class<T> type) {
return queryOperations.queryForObject(statement, type);
}
private ExecutionResult parseAndExecuteQuery(String statement) {
try {
CypherParser parser = new CypherParser();
Query query = parser.parse(statement);
return executionEngine.execute(query);
Iterable<Object> result = gremlinExecutor.query(statement, null);
return new QueryResultBuilder<Object>(result,resultConverter);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, e);
}

View File

@@ -24,8 +24,8 @@ import java.util.Map;
* @author mh
* @since 22.06.11
*/
public interface QueryEngine {
QueryResult<Map<String, Object>> query(String statement);
public interface QueryEngine<T> {
QueryResult<T> query(String statement, Map<String, Object> params);
public enum Type { Cypher, Gremlin }
}

View File

@@ -22,10 +22,10 @@ import java.util.Map;
* @author mh
* @since 28.06.11
*/
public interface QueryOperations {
Iterable<Map<String, Object>> queryForList(String statement);
public interface QueryOperations<R> {
Iterable<R> queryForList(String statement, Map<String,Object>...params);
<T> Iterable<T> query(String statement, Class<T> type);
<T> Iterable<T> query(String statement, Class<T> type, Map<String,Object>...params);
<T> T queryForObject(String statement, Class<T> type);
<T> T queryForObject(String statement, Class<T> type, Map<String,Object>...params);
}

View File

@@ -99,13 +99,40 @@ public interface Neo4jOperations {
*/
<T extends PropertyContainer> T index(String indexName, T element, String field, Object value);
/**
* Converts the Iterable into a QueryResult object for uniform handling. E.g.
* template.convert(node.getRelationships());
*/
<T> QueryResult<T> convert(Iterable<T> iterable);
QueryResult<Map<String, Object>> query(String statement);
/**
* Runs the given cypher statement and packages the result in a QueryResult, simple conversions via the
* registered converter-factories are already executed via this method.
*/
QueryResult<Map<String, Object>> query(String statement,Map<String,Object> params);
/**
* Executes the given Gremlin statement and returns the result packaged as QueryResult as Neo4j types, not
* Gremlin types. Table rows are converted to Map<String,Object>.
*/
QueryResult<Object> execute(String statement, Map<String,Object> params);
/**
* Traverses the graph starting at the given node with the provided traversal description. The Path's of the
* traversal will be packaged into a QueryResult which can be easily converted into Nodes, Relationships or
* Graph-Entities.
*/
QueryResult<Path> traverse(Node startNode, TraversalDescription traversal);
/**
* The value is looked up in the Neo4j index returning the IndexHits wrapped in a QueryResult to be converted
* into Paths or Entities.
*/
<T extends PropertyContainer> QueryResult<T> lookup(String indexName, String field, Object value);
<T extends PropertyContainer> QueryResult<T> lookup(String indexName, Object valueOrQueryObject);
/**
* The query is executed on the index returning the IndexHits wrapped in a QueryResult to be converted
* into Paths or Entities.
*/
<T extends PropertyContainer> QueryResult<T> lookup(String indexName, Object query);
}

View File

@@ -18,20 +18,13 @@ package org.springframework.data.neo4j.template;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.helpers.collection.ClosableIterable;
import org.neo4j.helpers.collection.IterableWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.conversion.QueryResult;
import org.springframework.data.neo4j.conversion.QueryResultBuilder;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.Property;
import org.springframework.data.neo4j.support.path.NodePath;
import org.springframework.data.neo4j.support.path.PathMapper;
import org.springframework.data.neo4j.support.path.RelationshipPath;
import org.springframework.data.neo4j.support.query.CypherQueryEngine;
import org.springframework.data.neo4j.support.query.QueryEngine;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
@@ -170,31 +163,10 @@ public class Neo4jTemplate implements Neo4jOperations {
return new QueryResultBuilder<T>(iterable);
}
private QueryEngine queryEngineFor(CypherQueryEngine.Type type) {
private QueryEngine queryEngineFor(QueryEngine.Type type) {
return graphDatabase.queryEngineFor(type);
}
private <T> ClosableIterable<T> mapNodes(final IndexHits<Node> nodes, final PathMapper<T> pathMapper) {
assert nodes != null;
assert pathMapper != null;
return new IndexHitsIterableWrapper<T,Node>(nodes, pathMapper) {
@Override
protected Path createPath(Node node) {
return new NodePath(node);
}
};
}
private <T> ClosableIterable<T> mapRelationships(final IndexHits<Relationship> relationships, final PathMapper<T> pathMapper) {
assert relationships != null;
assert pathMapper != null;
return new IndexHitsIterableWrapper<T,Relationship>(relationships, pathMapper) {
@Override
protected Path createPath(Relationship relationship) {
return new RelationshipPath(relationship);
}
};
}
@Override
public Relationship createRelationship(final Node startNode, final Node endNode, final RelationshipType relationshipType, final Property... properties) {
notNull(startNode, "startNode", endNode, "endNode", relationshipType, "relationshipType", properties, "properties");
@@ -206,33 +178,16 @@ public class Neo4jTemplate implements Neo4jOperations {
});
}
private static abstract class IndexHitsIterableWrapper<T, S extends PropertyContainer> extends IterableWrapper<T, S> implements ClosableIterable<T> {
private final IndexHits<S> indexHits;
private final PathMapper<T> pathMapper;
public IndexHitsIterableWrapper(IndexHits<S> indexHits, PathMapper<T> pathMapper) {
super(indexHits);
this.indexHits = indexHits;
this.pathMapper = pathMapper;
}
@Override
protected T underlyingObjectToObject(S node) {
return pathMapper.mapPath(createPath(node));
}
protected abstract Path createPath(S element);
@Override
public void close() {
indexHits.close();
}
@Override
public QueryResult<Map<String, Object>> query(String statement, Map<String, Object> params) {
notNull(statement, "statement");
return queryEngineFor(QueryEngine.Type.Cypher).query(statement, params);
}
@Override
public QueryResult<Map<String, Object>> query(String statement) {
public QueryResult<Object> execute(String statement, Map<String, Object> params) {
notNull(statement, "statement");
return queryEngineFor(QueryEngine.Type.Cypher).query(statement);
return queryEngineFor(QueryEngine.Type.Gremlin).query(statement, params);
}
@Override
@@ -256,11 +211,11 @@ public class Neo4jTemplate implements Neo4jOperations {
}
}
@Override
public <T extends PropertyContainer> QueryResult<T> lookup(String indexName, Object valueOrQueryObject) {
notNull(valueOrQueryObject, "valueOrQueryObject", indexName, "indexName");
public <T extends PropertyContainer> QueryResult<T> lookup(String indexName, Object query) {
notNull(query, "valueOrQueryObject", indexName, "indexName");
try {
Index<T> index = graphDatabase.getIndex(indexName);
return new QueryResultBuilder<T>(index.query(valueOrQueryObject));
return new QueryResultBuilder<T>(index.query(query));
} catch (RuntimeException e) {
throw translateExceptionIfPossible(e);
}

View File

@@ -21,15 +21,14 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.Node;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.helpers.collection.MapUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.neo4j.Person;
import org.springframework.data.neo4j.Personality;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.NodeBacked;
import org.springframework.data.neo4j.Person;
import org.springframework.data.neo4j.Personality;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.support.DelegatingGraphDatabase;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
import org.springframework.data.neo4j.support.TestTeam;
@@ -59,7 +58,7 @@ public class QueryEngineTest {
protected ConversionService conversionService;
@Autowired
private GraphDatabaseContext graphDatabaseContext;
private QueryEngine queryEngine;
private QueryEngine<Map<String,Object>> queryEngine;
private TestTeam testTeam;
private Person michael;
private GraphDatabase graphDatabase;
@@ -87,31 +86,35 @@ public class QueryEngineTest {
@Test
@Transactional
public void testQueryList() throws Exception {
final String queryString = String.format("start person=(%d,%d) return person.name, person.age", idFor(michael), idFor(testTeam.david));
final Collection<Map<String,Object>> result = IteratorUtil.asCollection(queryEngine.query(queryString));
final String queryString = "start person=(:michael,:david) return person.name, person.age";
final Collection<Map<String,Object>> result = IteratorUtil.asCollection(queryEngine.query(queryString, MapUtil.map("michael",idFor(michael), "david",idFor(testTeam.david))));
assertEquals(asList(testTeam.simpleRowFor(michael,"person"),testTeam.simpleRowFor(testTeam.david,"person")),result);
}
@Test
public void testQueryListOfTypeNode() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:boss]- (boss) return boss", michael.getName());
final Collection<Node> result = IteratorUtil.asCollection(queryEngine.query(queryString).to(Node.class));
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:boss]- (boss) return boss";
final Collection<Node> result = IteratorUtil.asCollection(queryEngine.query(queryString, michaelsName()).to(Node.class));
assertEquals(asList(nodeFor(testTeam.emil)),result);
}
@Test
public void testQueryListOfTypePerson() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:boss]- (boss) return boss", michael.getName());
final Collection<Person> result = IteratorUtil.asCollection(queryEngine.query(queryString).to(Person.class, new EntityResultConverter(graphDatabaseContext)));
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:boss]- (boss) return boss";
final Collection<Person> result = IteratorUtil.asCollection(queryEngine.query(queryString, michaelsName()).to(Person.class, new EntityResultConverter(graphDatabaseContext)));
assertEquals(asList(testTeam.emil),result);
}
private Map<String, Object> michaelsName() {
return MapUtil.map("name", michael.getName());
}
@Test
public void testQuerySingleOfTypePerson() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:boss]- (boss) return boss", michael.getName());
final Person result = queryEngine.query(queryString).to(Person.class, new EntityResultConverter<Map<String,Object>,Person>(graphDatabaseContext)).single();
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:boss]- (boss) return boss";
final Person result = queryEngine.query(queryString, michaelsName()).to(Person.class, new EntityResultConverter<Map<String,Object>,Person>(graphDatabaseContext)).single();
assertEquals(testTeam.emil,result);
}
@@ -119,7 +122,7 @@ public class QueryEngineTest {
@Test
public void testQueryListWithCustomConverter() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:boss]- (boss) return boss", michael.getName());
final Collection<String> result = IteratorUtil.asCollection(queryEngine.query(queryString).to(String.class, new ResultConverter<Map<String, Object>, String>() {
final Collection<String> result = IteratorUtil.asCollection(queryEngine.query(queryString, michaelsName()).to(String.class, new ResultConverter<Map<String, Object>, String>() {
@Override
public String convert(Map<String, Object> row, Class<String> target) {
return (String) ((Node) row.get("boss")).getProperty("name");
@@ -138,15 +141,15 @@ public class QueryEngineTest {
@Test
public void testQueryForObjectAsString() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:persons]- (team) return team.name", michael.getName());
final String result = queryEngine.query(queryString).to(String.class).single();
final String queryString = "start person=(name_index,name,\":name\") match (person) <-[:persons]- (team) return team.name";
final String result = queryEngine.query(queryString, michaelsName()).to(String.class).single();
assertEquals(testTeam.sdg.getName(),result);
}
@Test
public void testQueryForObjectAsEnum() throws Exception {
final String queryString = String.format("start person=(name_index,name,\"%s\") return person.personality", michael.getName());
final Personality result = queryEngine.query(queryString).to(Personality.class).single();
final String queryString = "start person=(name_index,name,\":name\") return person.personality";
final Personality result = queryEngine.query(queryString, michaelsName()).to(Personality.class).single();
assertEquals(michael.getPersonality(),result);
}

View File

@@ -23,7 +23,6 @@ import org.junit.runner.RunWith;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.Group;
import org.springframework.data.neo4j.*;
import org.springframework.data.neo4j.Person;
import org.springframework.data.neo4j.Personality;
import org.springframework.data.neo4j.support.GraphDatabaseContext;
@@ -49,7 +48,7 @@ import static org.junit.Assert.assertThat;
public class QueryExecutorTest {
@Autowired
GraphDatabaseContext graphDatabaseContext;
private QueryExecutor queryExecutor;
private CypherQueryExecutor queryExecutor;
private TestTeam testTeam;
private Person michael;
@@ -57,7 +56,7 @@ public class QueryExecutorTest {
public void setUp() throws Exception {
testTeam = new TestTeam();
testTeam.createSDGTeam();
queryExecutor = new QueryExecutor(graphDatabaseContext);
queryExecutor = new CypherQueryExecutor(graphDatabaseContext);
michael = testTeam.michael;
}

View File

@@ -59,7 +59,7 @@ public class QueryOperationsTest {
protected ConversionService conversionService;
@Autowired
private GraphDatabaseContext graphDatabaseContext;
private QueryOperations queryOperations;
private CypherQueryExecutor queryOperations;
private TestTeam testTeam;
private Person michael;
private GraphDatabase graphDatabase;
@@ -69,7 +69,8 @@ public class QueryOperationsTest {
graphDatabase = createGraphDatabase();
testTeam = new TestTeam();
testTeam.createSDGTeam();
queryOperations = new DefaultQueryOperations(graphDatabase.queryEngineFor(QueryEngine.Type.Cypher));
queryOperations = new CypherQueryExecutor(graphDatabaseContext);
//new DefaultQueryOperations<Map<String,Object>>(graphDatabase.queryEngineFor(QueryEngine.Type.Cypher));
michael = testTeam.michael;
}

View File

@@ -273,6 +273,16 @@ public class Neo4jTemplateApiTest {
assertSingleResult("node1",template.traverse(referenceNode, description).to(String.class,new PathNodeNameMapper()));
}
@Test
public void shouldFindNextNodeViaCypher() throws Exception {
assertSingleResult(node1, template.query("start n=(0) match n-->m return m", null).to(Node.class));
}
@Test
public void shouldFindNextNodeViaGremlin() throws Exception {
assertSingleResult(node1, template.execute("g.v(0).out", null).to(Node.class));
}
@Test
public void shouldGetDirectRelationship() throws Exception {
assertSingleResult("rel1", template.convert(referenceNode.getRelationships()).to(String.class, new RelationshipNameConverter()));

View File

@@ -27,9 +27,12 @@ Import-Template:
javax.validation.*;version="0";resolution:=optional,
javax.annotation.*;version="0";resolution:=optional,
javax.naming.*;version="0";resolution:=optional,
javax.script.*;version="0";resolution:=optional,
javax.persistence.*;version="[1.0.0, 3.0.0)";resolution:=optional,
javax.persistence.spi.*;version="[1.0.0, 3.0.0)";resolution:=optional,
javax.transaction.*;version="[1.0.1, 2.0.0)";resolution:=optional
Excluded-Imports:
javax.transaction.*;version="[1.0.1, 2.0.0)";resolution:=optional,
com.tinkerpop.blueprints.*;version="[0.8,1.0)";resolution:=optional,
com.tinkerpop.gremlin.*;version="[1.1,2.0)";resolution:=optional
Excluded-Imports:
org.neo4j.*.impl.*