Merge
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
Spring Data Graph Changelog
|
||||
===========================
|
||||
|
||||
Changes in version 1.1.0.RC1 (2011-07-25)
|
||||
-----------------------------------------
|
||||
* Added Gremlin support (embedded & REST)
|
||||
* QueryEngine.query method now takes a parameter map (for cypher and gremlin)
|
||||
* documentation updates
|
||||
|
||||
|
||||
Changes in version 1.1.0.M2 (2011-07-20)
|
||||
-----------------------------------------
|
||||
* updated dependency to Neo4j 1.4
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
<org.mockito.version>1.8.4</org.mockito.version>
|
||||
<org.slf4j.version>1.5.10</org.slf4j.version>
|
||||
<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
|
||||
<data.commons.version>1.1.0.RC1</data.commons.version>
|
||||
<data.commons.version>1.1.0.RELEASE</data.commons.version>
|
||||
<neo4j.version>1.4</neo4j.version>
|
||||
<aspectj.version>1.6.12.M1</aspectj.version>
|
||||
<blueprints.version>0.8</blueprints.version>
|
||||
<gremlin.version>1.1</gremlin.version>
|
||||
</properties>
|
||||
<profiles>
|
||||
<profile>
|
||||
@@ -184,6 +186,27 @@
|
||||
<scope>runtime</scope>
|
||||
</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>
|
||||
<version>${blueprints.version}</version>
|
||||
<scope>optional</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.tinkerpop</groupId>
|
||||
<artifactId>gremlin</artifactId>
|
||||
<version>${gremlin.version}</version>
|
||||
<scope>optional</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>jsr250-api</artifactId>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -17,16 +17,19 @@
|
||||
package org.springframework.data.neo4j.rest;
|
||||
|
||||
|
||||
import org.neo4j.helpers.collection.MapUtil;
|
||||
import org.springframework.data.neo4j.conversion.*;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @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,19 +44,14 @@ public class RestCypherQueryEngine implements QueryEngine {
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResult<Map<String, Object>> query(String statement) {
|
||||
return executeStatement(statement);
|
||||
}
|
||||
|
||||
private RestQueryResult executeStatement(String statement) {
|
||||
final RequestResult requestResult = restRequest.get("ext/CypherPlugin/graphdb/execute_query", JsonHelper.createJsonFrom(Collections.singletonMap("query", statement)));
|
||||
public QueryResult<Map<String, Object>> query(String statement, Map<String, Object> params) {
|
||||
final String parametrizedStatement = QueryResultBuilder.replaceParams(statement, params);
|
||||
final RequestResult requestResult = restRequest.get("ext/CypherPlugin/graphdb/execute_query", JsonHelper.createJsonFrom(MapUtil.map("query", parametrizedStatement)));
|
||||
return new RestQueryResult(restRequest.toMap(requestResult),restGraphDatabase,resultConverter);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.neo4j.rest;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class RestEntityExtractor {
|
||||
private final RestGraphDatabase restGraphDatabase;
|
||||
|
||||
public RestEntityExtractor(RestGraphDatabase restGraphDatabase) {
|
||||
this.restGraphDatabase = restGraphDatabase;
|
||||
}
|
||||
|
||||
Object convertFromRepresentation(Object value) {
|
||||
if (value instanceof Map) {
|
||||
RestEntity restEntity = createRestEntity((Map) value);
|
||||
if (restEntity != null) return restEntity;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ 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.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.conversion.ResultConverter;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
import org.springframework.data.neo4j.rest.index.RestIndexManager;
|
||||
@@ -103,8 +104,12 @@ public class RestGraphDatabase implements GraphDatabaseService, GraphDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEngine queryEngineFor(QueryEngine.Type type) {
|
||||
return new RestCypherQueryEngine(this, createResultConverter());
|
||||
public QueryEngine queryEngineFor(QueryType type) {
|
||||
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() {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.neo4j.rest;
|
||||
|
||||
|
||||
import org.neo4j.helpers.collection.IterableWrapper;
|
||||
import org.neo4j.helpers.collection.MapUtil;
|
||||
import org.springframework.data.neo4j.conversion.*;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 22.06.11
|
||||
*/
|
||||
public class RestGremlinQueryEngine implements QueryEngine<Object> {
|
||||
private final RestRequest restRequest;
|
||||
private final RestGraphDatabase restGraphDatabase;
|
||||
private final ResultConverter resultConverter;
|
||||
|
||||
public RestGremlinQueryEngine(RestGraphDatabase restGraphDatabase) {
|
||||
this(restGraphDatabase,null);
|
||||
}
|
||||
public RestGremlinQueryEngine(RestGraphDatabase restGraphDatabase, ResultConverter resultConverter) {
|
||||
this.restGraphDatabase = restGraphDatabase;
|
||||
this.resultConverter = resultConverter!=null ? resultConverter : new DefaultConverter();
|
||||
this.restRequest = restGraphDatabase.getRestRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResult<Object> query(String statement, Map<String, Object> params) {
|
||||
final String data = JsonHelper.createJsonFrom(MapUtil.map("script", statement,"params",params));
|
||||
final RequestResult requestResult = restRequest.get("ext/GremlinPlugin/graphdb/execute_script", data);
|
||||
return new RestQueryResult(JsonHelper.readJson(requestResult.getEntity()),restGraphDatabase,resultConverter);
|
||||
}
|
||||
|
||||
static class RestQueryResult<T> implements QueryResult<T> {
|
||||
QueryResultBuilder<T> result;
|
||||
private final RestGraphDatabase restGraphDatabase;
|
||||
|
||||
|
||||
@Override
|
||||
public <R> ConvertedResult<R> to(Class<R> type) {
|
||||
return result.to(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> ConvertedResult<R> to(Class<R> type, ResultConverter<T, R> converter) {
|
||||
return result.to(type,converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Handler<T> handler) {
|
||||
result.handle(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return result.iterator();
|
||||
}
|
||||
|
||||
public RestQueryResult(Object result, RestGraphDatabase restGraphDatabase, ResultConverter resultConverter) {
|
||||
this.restGraphDatabase = restGraphDatabase;
|
||||
final Iterable<T> convertedResult = convertRestResult(result);
|
||||
this.result=new QueryResultBuilder<T>(convertedResult, resultConverter);
|
||||
}
|
||||
|
||||
private Iterable<T> convertRestResult(Object result) {
|
||||
final RestEntityExtractor restEntityExtractor = new RestEntityExtractor(restGraphDatabase);
|
||||
if (result instanceof Map) {
|
||||
Map<?,?> mapResult= (Map<?, ?>) result;
|
||||
if (RestResultException.isExceptionResult(mapResult)) {
|
||||
throw new RestResultException(mapResult);
|
||||
}
|
||||
if (isTableResult(mapResult)) {
|
||||
return (Iterable<T>) new RestTableResultExtractor(restEntityExtractor).extract(mapResult);
|
||||
}
|
||||
}
|
||||
if (result instanceof Iterable) {
|
||||
return new IterableWrapper<T,Object>((Iterable<Object>)result) {
|
||||
@Override
|
||||
protected T underlyingObjectToObject(Object value) {
|
||||
return (T) restEntityExtractor.convertFromRepresentation(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
return Collections.singletonList((T) restEntityExtractor.convertFromRepresentation(result));
|
||||
}
|
||||
|
||||
public static boolean isTableResult(Map<?, ?> mapResult) {
|
||||
return mapResult.containsKey("columns") && mapResult.containsKey("data");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.neo4j.rest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 25.07.11
|
||||
*/
|
||||
public class RestResultException extends RuntimeException {
|
||||
public RestResultException(Map<?, ?> result) {
|
||||
super(format(result));
|
||||
}
|
||||
|
||||
private static String format(Map<?, ?> result) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(result.get("message")).append(" at\n");
|
||||
sb.append(result.get("exception")).append("\n");
|
||||
List<String> stacktrace = (List<String>) result.get("stacktrace");
|
||||
if (stacktrace != null) {
|
||||
for (String line : stacktrace) {
|
||||
sb.append(" ").append(line).append("\n");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static boolean isExceptionResult(Map<?, ?> result) {
|
||||
return result.containsKey("exception") && result.containsKey("message");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright 2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.neo4j.rest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 25.07.11
|
||||
*/
|
||||
public class RestTableResultExtractor {
|
||||
|
||||
private final RestEntityExtractor restEntityExtractor;
|
||||
|
||||
public RestTableResultExtractor(RestEntityExtractor restEntityExtractor) {
|
||||
this.restEntityExtractor = restEntityExtractor;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> extract(Map<?, ?> restResult) {
|
||||
List<String> columns = (List<String>) restResult.get("columns");
|
||||
return extractData(restResult, columns);
|
||||
}
|
||||
|
||||
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), restEntityExtractor.convertFromRepresentation(value));
|
||||
}
|
||||
return newRow;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
import org.springframework.data.neo4j.rest.RestGraphDatabase;
|
||||
import org.springframework.data.neo4j.support.query.QueryOperationsTest;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngineTest;
|
||||
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
@@ -39,7 +39,7 @@ import org.springframework.test.context.transaction.TransactionalTestExecutionLi
|
||||
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/support/Neo4jGraphPersistenceTest-context.xml",
|
||||
"classpath:RestTest-context.xml"})
|
||||
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
|
||||
public class RestQueryEngineTest extends QueryOperationsTest {
|
||||
public class RestQueryEngineTest extends QueryEngineTest {
|
||||
|
||||
@Autowired
|
||||
RestGraphDatabase restGraphDatabase;
|
||||
|
||||
@@ -129,9 +129,26 @@
|
||||
<dependency>
|
||||
<groupId>org.neo4j</groupId>
|
||||
<artifactId>server-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
<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>
|
||||
|
||||
@@ -36,7 +36,7 @@ import java.lang.annotation.Target;
|
||||
@Target({ElementType.FIELD,ElementType.METHOD})
|
||||
public @interface Query {
|
||||
/**
|
||||
* @return Query to be executed %d will be replaced by the node-id of the current entity other placeholders by the given params
|
||||
* @return Query to be executed %start will be replaced by the node-id of the current entity other placeholders (%name) by the given named params
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
@@ -46,7 +46,9 @@ public @interface Query {
|
||||
Class<?> elementClass() default Object.class;
|
||||
|
||||
/**
|
||||
* @return parameters that are replaced in the to the @see query-string
|
||||
* @return tuple list of parameters that are replaced in the to the @see query-string {"name", value}
|
||||
*/
|
||||
String[] params() default {};
|
||||
|
||||
QueryType type() default QueryType.Cypher;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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.annotation;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 25.07.11
|
||||
*/
|
||||
public enum QueryType {
|
||||
Cypher, Gremlin
|
||||
}
|
||||
@@ -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()+"\\b",""+param.getValue());
|
||||
}
|
||||
return statement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> ConvertedResult<R> to(Class<R> type) {
|
||||
return this.to(type, defaultConverter);
|
||||
|
||||
@@ -23,7 +23,7 @@ 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.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -99,7 +99,7 @@ public interface GraphDatabase {
|
||||
*/
|
||||
TraversalDescription createTraversalDescription();
|
||||
|
||||
QueryEngine queryEngineFor(CypherQueryEngine.Type type);
|
||||
<T> QueryEngine<T> queryEngineFor(QueryType type);
|
||||
|
||||
void setConversionService(ConversionService conversionService);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ package org.springframework.data.neo4j.core;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.traversal.TraversalDescription;
|
||||
import org.springframework.data.neo4j.support.node.Neo4jNodeBacking;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface introduced to objects annotated with @NodeEntity by the {@link org.springframework.data.neo4j.support.node.Neo4jNodeBacking} aspect.
|
||||
@@ -125,6 +129,11 @@ public interface NodeBacked extends GraphBacked<Node> {
|
||||
<R extends RelationshipBacked> R getRelationshipTo(NodeBacked target, Class<R> relationshipClass, String type);
|
||||
|
||||
|
||||
<T> Iterable<T> findAllByQuery(final String query, final Class<T> targetType,Map<String,Object> params);
|
||||
|
||||
Iterable<Map<String,Object>> findAllByQuery(final String query,Map<String,Object> params);
|
||||
|
||||
<T> T findByQuery(final String query, final Class<T> targetType,Map<String,Object> params);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.data.neo4j.core.NodeBacked;
|
||||
import org.springframework.data.neo4j.support.GenericTypeExtractor;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
|
||||
@@ -48,13 +49,16 @@ public class QueryFieldAccessorFactory implements FieldAccessorFactory<NodeBacke
|
||||
protected final Field field;
|
||||
private final String query;
|
||||
private Class<?> target;
|
||||
protected String[] params;
|
||||
protected String[] annotationParams;
|
||||
private boolean iterableResult;
|
||||
|
||||
public QueryFieldAccessor(final Field field) {
|
||||
this.field = field;
|
||||
final Query query = field.getAnnotation(Query.class);
|
||||
this.params = query.params();
|
||||
this.annotationParams = query.params();
|
||||
if ((this.annotationParams.length % 2) != 0) {
|
||||
throw new IllegalArgumentException("Number of parameters has to be even to construct a parameter map");
|
||||
}
|
||||
this.query = query.value();
|
||||
this.iterableResult = Iterable.class.isAssignableFrom(field.getType());
|
||||
this.target = resolveTarget(query,field);
|
||||
@@ -77,23 +81,23 @@ public class QueryFieldAccessorFactory implements FieldAccessorFactory<NodeBacke
|
||||
|
||||
@Override
|
||||
public Object getValue(final NodeBacked nodeBacked) {
|
||||
final String queryString = String.format(this.query, (Object[])createPlaceholderParams(nodeBacked));
|
||||
return doReturn(executeQuery(nodeBacked, queryString));
|
||||
return doReturn(executeQuery(nodeBacked, this.query, createPlaceholderParams(nodeBacked)));
|
||||
}
|
||||
|
||||
private Object executeQuery(NodeBacked nodeBacked, String queryString) {
|
||||
if (!iterableResult) return nodeBacked.findByQuery(queryString,this.target);
|
||||
if (Map.class.isAssignableFrom(target)) return nodeBacked.findAllByQuery(queryString);
|
||||
return nodeBacked.findAllByQuery(queryString, this.target);
|
||||
private Object executeQuery(NodeBacked nodeBacked, String queryString, Map<String, Object> params) {
|
||||
if (!iterableResult) return nodeBacked.findByQuery(queryString,this.target,params);
|
||||
if (Map.class.isAssignableFrom(target)) return nodeBacked.findAllByQuery(queryString,params);
|
||||
return nodeBacked.findAllByQuery(queryString, this.target,params);
|
||||
}
|
||||
|
||||
private Object[] createPlaceholderParams(NodeBacked nodeBacked) {
|
||||
if (params.length==0) return new Object[] {nodeBacked.getNodeId()};
|
||||
|
||||
final Object[] parameters = new Object[1 + this.params.length];
|
||||
parameters[0]=nodeBacked.getNodeId();
|
||||
System.arraycopy(this.params,0,parameters,1,this.params.length);
|
||||
return parameters;
|
||||
private Map<String, Object> createPlaceholderParams(NodeBacked nodeBacked) {
|
||||
Map<String,Object> params=new HashMap<String, Object>();
|
||||
params.put("start",nodeBacked.getNodeId());
|
||||
if (annotationParams.length==0) return params;
|
||||
for (int i = 0; i < annotationParams.length; i+=2) {
|
||||
params.put(annotationParams[i],annotationParams[i+1]);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,11 @@ package org.springframework.data.neo4j.fieldaccess;
|
||||
import org.neo4j.graphdb.traversal.TraversalDescription;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.neo4j.annotation.GraphTraversal;
|
||||
import org.springframework.data.neo4j.annotation.Query;
|
||||
import org.springframework.data.neo4j.core.FieldTraversalDescriptionBuilder;
|
||||
import org.springframework.data.neo4j.core.NodeBacked;
|
||||
import org.springframework.data.neo4j.repository.DirectGraphRepositoryFactory;
|
||||
import org.springframework.data.neo4j.support.GenericTypeExtractor;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -56,11 +58,19 @@ public class TraversalFieldAccessorFactory implements FieldAccessorFactory<NodeB
|
||||
public TraversalFieldAccessor(final Field field) {
|
||||
this.field = field;
|
||||
final GraphTraversal graphEntityTraversal = field.getAnnotation(GraphTraversal.class);
|
||||
this.target = graphEntityTraversal.elementClass();
|
||||
this.target = resolveTarget(graphEntityTraversal,field);
|
||||
this.params = graphEntityTraversal.params();
|
||||
this.fieldTraversalDescriptionBuilder = createTraversalDescription(graphEntityTraversal);
|
||||
}
|
||||
|
||||
private Class<? extends NodeBacked> resolveTarget(GraphTraversal graphTraversal, Field field) {
|
||||
if (!graphTraversal.elementClass().equals(Object.class)) return graphTraversal.elementClass();
|
||||
final Class<?> result = GenericTypeExtractor.resolveFieldType(field);
|
||||
if (!NodeBacked.class.isAssignableFrom(result)) throw new IllegalArgumentException("The target result type of the traversal is no node entity: "+field);
|
||||
return (Class<? extends NodeBacked>) result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isWriteable(NodeBacked nodeBacked) {
|
||||
return false;
|
||||
|
||||
@@ -20,14 +20,17 @@ import org.neo4j.helpers.collection.IteratorUtil;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.annotation.Query;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.Query;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.annotation.RelationshipEntity;
|
||||
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.conversion.EntityResultConverter;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
import org.springframework.data.neo4j.support.query.GremlinQueryEngine;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -38,10 +41,13 @@ import org.springframework.util.Assert;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
|
||||
import static org.springframework.data.neo4j.annotation.QueryType.Cypher;
|
||||
import static org.springframework.data.neo4j.annotation.QueryType.Gremlin;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
@@ -109,11 +115,7 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
@Override
|
||||
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repositoryMetadata, NamedQueries namedQueries) {
|
||||
final GraphQueryMethod queryMethod = new GraphQueryMethod(method, repositoryMetadata,namedQueries);
|
||||
|
||||
if (queryMethod.isValid()) {
|
||||
return new GraphRepositoryQuery(queryMethod, repositoryMetadata, graphDatabaseContext);
|
||||
}
|
||||
return null;
|
||||
return queryMethod.createQuery(repositoryMetadata, GraphRepositoryFactory.this.graphDatabaseContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -150,19 +152,28 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private String prepareQuery(Object[] args) {
|
||||
final Parameters parameters = getParameters();
|
||||
Object[] resolvedParameters = resolveParameters(args,parameters.getBindableParameters());
|
||||
String baseQuery = String.format(query, (Object[]) resolvedParameters);
|
||||
String queryString = this.query;
|
||||
if (parameters.hasSortParameter()) {
|
||||
baseQuery = addSorting(baseQuery, (Sort) args[parameters.getSortIndex()]);
|
||||
queryString = addSorting(queryString, (Sort) args[parameters.getSortIndex()]);
|
||||
}
|
||||
if (parameters.hasPageableParameter()) {
|
||||
final Pageable pageable = getPageable(args);
|
||||
if (pageable!=null) {
|
||||
baseQuery = addSorting(baseQuery, pageable.getSort());
|
||||
baseQuery = addPaging(baseQuery, pageable);
|
||||
queryString = addSorting(queryString, pageable.getSort());
|
||||
queryString = addPaging(queryString, pageable);
|
||||
}
|
||||
}
|
||||
return baseQuery;
|
||||
return queryString;
|
||||
}
|
||||
|
||||
|
||||
private Map<String, Object> resolveParams(Object[] parameters) {
|
||||
Map<String,Object> params=new HashMap<String, Object>();
|
||||
for (Parameter parameter : getParameters().getBindableParameters()) {
|
||||
final Object value = parameters[parameter.getIndex()];
|
||||
params.put(parameter.getName(),resolveParameter(value));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private Pageable getPageable(Object[] args) {
|
||||
@@ -191,17 +202,6 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object[] resolveParameters(Object[] parameters, Parameters bindableParameters) {
|
||||
final int paramCount = bindableParameters.getNumberOfParameters();
|
||||
final Object[] result = new Object[paramCount];
|
||||
for (int i = 0; i < paramCount; i++) {
|
||||
final Parameter parameter = bindableParameters.getParameter(i);
|
||||
final Object value = parameters[parameter.getIndex()];
|
||||
result[i] = resolveParameter(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object resolveParameter(Object parameter) {
|
||||
if (parameter instanceof NodeBacked) {
|
||||
return ((NodeBacked)parameter).getNodeId();
|
||||
@@ -232,57 +232,111 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
public boolean hasAnnotation() {
|
||||
return queryAnnotation!=null;
|
||||
}
|
||||
|
||||
private boolean isIterableResult() {
|
||||
return Iterable.class.isAssignableFrom(getReturnType());
|
||||
}
|
||||
|
||||
private RepositoryQuery createQuery(RepositoryMetadata repositoryMetadata, final GraphDatabaseContext context) {
|
||||
if (!isValid()) return null;
|
||||
if (queryAnnotation == null) {
|
||||
return new CypherGraphRepositoryQuery(this, repositoryMetadata, context); // cypher is default for named queries
|
||||
}
|
||||
switch (queryAnnotation.type()) {
|
||||
case Cypher:
|
||||
return new CypherGraphRepositoryQuery(this, repositoryMetadata, context);
|
||||
case Gremlin:
|
||||
return new GremlinGraphRepositoryQuery(this, repositoryMetadata, context);
|
||||
default:
|
||||
throw new IllegalStateException("@Query Annotation has to be configured as Cypher or Gremlin Query");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class GraphRepositoryQuery implements RepositoryQuery {
|
||||
private QueryExecutor queryExecutor;
|
||||
|
||||
private static class CypherGraphRepositoryQuery extends GraphRepositoryQuery {
|
||||
|
||||
private CypherQueryExecutor queryExecutor;
|
||||
|
||||
public CypherGraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final GraphDatabaseContext graphDatabaseContext) {
|
||||
super(queryMethod, metadata, graphDatabaseContext);
|
||||
queryExecutor = new CypherQueryExecutor(graphDatabaseContext);
|
||||
}
|
||||
|
||||
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
GraphQueryMethod queryMethod = getQueryMethod();
|
||||
final Class<?> compoundType = queryMethod.getCompoundType();
|
||||
final QueryMethod.Type queryResultType = queryMethod.getType();
|
||||
if (queryResultType== QueryMethod.Type.PAGING) {
|
||||
return queryPaged(queryString,params,pageable);
|
||||
}
|
||||
if (queryMethod.isIterableResult()) {
|
||||
if (compoundType.isAssignableFrom(Map.class)) return queryExecutor.queryForList(queryString,params);
|
||||
return queryExecutor.query(queryString, queryMethod.getCompoundType(),params);
|
||||
}
|
||||
return queryExecutor.queryForObject(queryString, queryMethod.getReturnType(),params);
|
||||
}
|
||||
private Object queryPaged(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
final Iterable<?> result = queryExecutor.query(queryString, getQueryMethod().getCompoundType(),params);
|
||||
return createPage(result, pageable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GremlinGraphRepositoryQuery extends GraphRepositoryQuery {
|
||||
|
||||
private GremlinQueryEngine queryExecutor;
|
||||
|
||||
public GremlinGraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final GraphDatabaseContext graphDatabaseContext) {
|
||||
super(queryMethod, metadata, graphDatabaseContext);
|
||||
queryExecutor = new GremlinQueryEngine(graphDatabaseContext.getGraphDatabaseService(), new EntityResultConverter(graphDatabaseContext));
|
||||
}
|
||||
|
||||
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
GraphQueryMethod queryMethod = getQueryMethod();
|
||||
final QueryMethod.Type queryResultType = queryMethod.getType();
|
||||
if (queryResultType== QueryMethod.Type.PAGING) {
|
||||
return queryPaged(queryString,params,pageable);
|
||||
}
|
||||
if (queryMethod.isIterableResult()) {
|
||||
return queryExecutor.query(queryString,params).to(queryMethod.getCompoundType());
|
||||
}
|
||||
return queryExecutor.query(queryString, params).to(queryMethod.getReturnType()).single();
|
||||
}
|
||||
|
||||
private Object queryPaged(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
final Iterable<?> result = queryExecutor.query(queryString, params).to(getQueryMethod().getCompoundType());
|
||||
return createPage(result, pageable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static abstract class GraphRepositoryQuery implements RepositoryQuery {
|
||||
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);
|
||||
this.queryMethod = queryMethod;
|
||||
this.metadata = metadata;
|
||||
this.iterableResult = Iterable.class.isAssignableFrom(queryMethod.getReturnType());
|
||||
this.compoundType = queryMethod.getCompoundType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
Map<String, Object> params = queryMethod.resolveParams(parameters);
|
||||
final String queryString = queryMethod.prepareQuery(parameters);
|
||||
return dispatchQuery(queryString,queryMethod.getPageable(parameters));
|
||||
return dispatchQuery(queryString,params,queryMethod.getPageable(parameters));
|
||||
}
|
||||
|
||||
private Object dispatchQuery(String queryString, Pageable pageable) {
|
||||
final QueryMethod.Type queryResultType = queryMethod.getType();
|
||||
if (queryResultType== QueryMethod.Type.PAGING) {
|
||||
return queryPaged(queryString,pageable);
|
||||
}
|
||||
if (iterableResult) {
|
||||
if (compoundType.isAssignableFrom(Map.class)) return queryExecutor.queryForList(queryString);
|
||||
return queryExecutor.query(queryString, queryMethod.getCompoundType());
|
||||
}
|
||||
return queryExecutor.queryForObject(queryString, queryMethod.getReturnType());
|
||||
}
|
||||
|
||||
private Object queryPaged(String queryString, Pageable pageable) {
|
||||
final Iterable<?> result = queryExecutor.query(queryString, queryMethod.getCompoundType());
|
||||
return createPage(result, pageable);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Object createPage(Iterable<?> result, Pageable pageable) {
|
||||
final List resultList = IteratorUtil.addToCollection(result, new ArrayList());
|
||||
if (pageable==null) return new PageImpl(resultList);
|
||||
final int currentTotal = pageable.getOffset() + pageable.getPageSize();
|
||||
return new PageImpl(resultList, pageable, currentTotal);
|
||||
}
|
||||
protected abstract Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable);
|
||||
|
||||
@Override
|
||||
public GraphQueryMethod getQueryMethod() {
|
||||
return queryMethod;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected Object createPage(Iterable<?> result, Pageable pageable) {
|
||||
final List resultList = IteratorUtil.addToCollection(result, new ArrayList());
|
||||
if (pageable==null) return new PageImpl(resultList);
|
||||
final int currentTotal = pageable.getOffset() + pageable.getPageSize();
|
||||
return new PageImpl(resultList, pageable, currentTotal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,11 @@ import org.neo4j.graphdb.traversal.TraversalDescription;
|
||||
import org.neo4j.index.impl.lucene.LuceneIndexImplementation;
|
||||
import org.neo4j.kernel.Traversal;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
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;
|
||||
@@ -125,11 +127,12 @@ public class DelegatingGraphDatabase implements GraphDatabase {
|
||||
return Traversal.description();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryEngine queryEngineFor(QueryEngine.Type type) {
|
||||
if (type == QueryEngine.Type.Cypher)
|
||||
return new CypherQueryEngine(delegate, createResultConverter());
|
||||
throw new IllegalArgumentException("Could not resolve query engine for "+type);
|
||||
public <T> QueryEngine<T> queryEngineFor(QueryType 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() {
|
||||
|
||||
@@ -73,7 +73,7 @@ public class GraphDatabaseContext {
|
||||
Map<String, String> config = fullText ? LuceneIndexImplementation.FULLTEXT_CONFIG : null;
|
||||
if (NodeBacked.class.isAssignableFrom(type)) return (Index<S>) getIndexManager().forNodes(indexName, config);
|
||||
if (RelationshipBacked.class.isAssignableFrom(type)) return (Index<S>) getIndexManager().forRelationships(indexName, config);
|
||||
throw new IllegalArgumentException("Wrong index type supplied: " + type);
|
||||
throw new IllegalArgumentException("Wrong index type supplied: " + type+" expected Node- or Relationship-Entity");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,8 +240,6 @@ public class GraphDatabaseContext {
|
||||
return graphDatabaseService.getRelationshipById(id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public GraphDatabaseService getGraphDatabaseService() {
|
||||
return graphDatabaseService;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.data.neo4j.annotation.RelationshipEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelatedTo;
|
||||
import org.springframework.data.neo4j.annotation.GraphProperty;
|
||||
import org.springframework.data.neo4j.annotation.GraphId;
|
||||
import org.springframework.data.neo4j.annotation.Query;
|
||||
import org.springframework.data.neo4j.annotation.RelatedToVia;
|
||||
import org.springframework.data.neo4j.annotation.GraphTraversal;
|
||||
|
||||
@@ -42,7 +43,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;
|
||||
@@ -71,6 +72,7 @@ public aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMixinFields<No
|
||||
declare @field: @RelatedToVia * (@Entity @NodeEntity(partial=true) *).*:@Transient;
|
||||
declare @field: @GraphId * (@Entity @NodeEntity(partial=true) *).*:@Transient;
|
||||
declare @field: @GraphTraversal * (@Entity @NodeEntity(partial=true) *).*:@Transient;
|
||||
declare @field: @Query * (@Entity @NodeEntity(partial=true) *).*:@Transient;
|
||||
|
||||
|
||||
|
||||
@@ -183,19 +185,20 @@ public aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMixinFields<No
|
||||
final Traverser traverser = traversalDescription.traverse(this.getPersistentState());
|
||||
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);
|
||||
return executor.query(query, targetType);
|
||||
|
||||
public <T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType, Map<String,Object> params) {
|
||||
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
|
||||
return executor.query(query, targetType,params);
|
||||
}
|
||||
|
||||
public Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query) {
|
||||
final QueryExecutor executor = new QueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
|
||||
return executor.queryForList(query);
|
||||
public Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query,Map<String,Object> params) {
|
||||
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
|
||||
return executor.queryForList(query,params);
|
||||
}
|
||||
|
||||
public <T> T NodeBacked.findByQuery(final String query, final Class<T> targetType) {
|
||||
final QueryExecutor executor = new QueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
|
||||
return executor.queryForObject(query, targetType);
|
||||
public <T> T NodeBacked.findByQuery(final String query, final Class<T> targetType,Map<String,Object> params) {
|
||||
final CypherQueryExecutor executor = new CypherQueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
|
||||
return executor.queryForObject(query, targetType,params);
|
||||
}
|
||||
|
||||
public <S extends NodeBacked, E extends NodeBacked> Iterable<EntityPath<S,E>> NodeBacked.findAllPathsByTraversal(TraversalDescription traversalDescription) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -19,6 +19,7 @@ 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.Map;
|
||||
|
||||
/**
|
||||
@@ -26,24 +27,27 @@ 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) return Collections.emptyMap();
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 org.neo4j.graphdb.GraphDatabaseService;
|
||||
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 java.util.Map;
|
||||
|
||||
public class GremlinQueryEngine implements QueryEngine<Object> {
|
||||
|
||||
private final GremlinExecutor gremlinExecutor;
|
||||
private final ResultConverter resultConverter;
|
||||
|
||||
public GremlinQueryEngine(GraphDatabaseService graphDatabaseService) {
|
||||
this(graphDatabaseService, new DefaultConverter());
|
||||
}
|
||||
|
||||
|
||||
public GremlinQueryEngine(GraphDatabaseService graphDatabaseService, ResultConverter resultConverter) {
|
||||
this.resultConverter = resultConverter != null ? resultConverter : new DefaultConverter();
|
||||
this.gremlinExecutor = new GremlinExecutor(graphDatabaseService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResult<Object> query(String statement, Map<String, Object> params) {
|
||||
try {
|
||||
Iterable<Object> result = gremlinExecutor.query(statement, params);
|
||||
return new QueryResultBuilder<Object>(result,resultConverter);
|
||||
} catch (Exception e) {
|
||||
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@ 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 }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -98,15 +98,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);
|
||||
|
||||
Node createNode();
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -18,17 +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.annotation.QueryType;
|
||||
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.support.path.PathMapper;
|
||||
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;
|
||||
@@ -167,7 +163,7 @@ public class Neo4jTemplate implements Neo4jOperations {
|
||||
return new QueryResultBuilder<T>(iterable);
|
||||
}
|
||||
|
||||
private QueryEngine queryEngineFor(CypherQueryEngine.Type type) {
|
||||
private QueryEngine queryEngineFor(QueryType type) {
|
||||
return graphDatabase.queryEngineFor(type);
|
||||
}
|
||||
|
||||
@@ -182,38 +178,15 @@ public class Neo4jTemplate implements Neo4jOperations {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createNode() {
|
||||
return createNode(null);
|
||||
}
|
||||
|
||||
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) {
|
||||
public QueryResult<Map<String, Object>> query(String statement, Map<String, Object> params) {
|
||||
notNull(statement, "statement");
|
||||
return queryEngineFor(QueryEngine.Type.Cypher).query(statement);
|
||||
return queryEngineFor(QueryType.Cypher).query(statement, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResult<Object> execute(String statement, Map<String, Object> params) {
|
||||
notNull(statement, "statement");
|
||||
return queryEngineFor(QueryType.Gremlin).query(statement, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -237,11 +210,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);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
http\://www.springframework.org/schema/data/graph/datagraph-1.0.xsd=org/springframework/data/graph/neo4j/config/datagraph-1.0.xsd
|
||||
http\://www.springframework.org/schema/data/graph/datagraph-1.0.xsd=org/springframework/data/neo4j/config/datagraph-1.0.xsd
|
||||
@@ -66,16 +66,16 @@ public class Person {
|
||||
@RelatedToVia(type = "knows", elementClass = Friendship.class)
|
||||
private Iterable<Friendship> friendships;
|
||||
|
||||
@Query(value = "start person=(%d) match (person)<-[:boss]-(boss) return boss")
|
||||
@Query(value = "start person=(%start) match (person)<-[:boss]-(boss) return boss")
|
||||
private Person bossByQuery;
|
||||
|
||||
@Query(value = "start person=(%d) match (person)<-[:boss]-(boss) return boss.%s",params = "name")
|
||||
@Query(value = "start person=(%start) match (person)<-[:boss]-(boss) return boss.%property",params = {"property","name"})
|
||||
private String bossName;
|
||||
|
||||
@Query(value = "start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member",elementClass = Person.class)
|
||||
@Query(value = "start person=(%start) match (person)<-[:persons]-(team)-[:persons]->(member) return member",elementClass = Person.class)
|
||||
private Iterable<Person> otherTeamMembers;
|
||||
|
||||
@Query(value = "start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member.name, member.age")
|
||||
@Query(value = "start person=(%start) match (person)<-[:persons]-(team)-[:persons]->(member) return member.name, member.age")
|
||||
private Iterable<Map<String,Object>> otherTeamMemberData;
|
||||
|
||||
public String getBossName() {
|
||||
|
||||
@@ -20,8 +20,10 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.annotation.Query;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.repository.GraphRepository;
|
||||
import org.springframework.data.neo4j.repository.NamedIndexRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -31,19 +33,22 @@ import java.util.Map;
|
||||
*/
|
||||
public interface PersonRepository extends GraphRepository<Person>, NamedIndexRepository<Person> {
|
||||
|
||||
@Query("start team=(%d) match (team)-[:persons]->(member) return member")
|
||||
Iterable<Person> findAllTeamMembers(Group team);
|
||||
@Query("start team=(%team) match (team)-[:persons]->(member) return member")
|
||||
Iterable<Person> findAllTeamMembers(@Param("team") Group team);
|
||||
|
||||
@Query("start team=(%d) match (team)-[:persons]->(member) return member.name,member.age")
|
||||
Iterable<Map<String,Object>> findAllTeamMemberData(Group team);
|
||||
@Query(value = "g.v(team).out('persons')", type = QueryType.Gremlin)
|
||||
Iterable<Person> findAllTeamMembersGremlin(@Param("team") Group team);
|
||||
|
||||
@Query("start person=(%d) match (boss)-[:boss]->(person) return boss")
|
||||
Person findBoss(Person person);
|
||||
@Query("start team=(%team) match (team)-[:persons]->(member) return member.name,member.age")
|
||||
Iterable<Map<String,Object>> findAllTeamMemberData(@Param("team") Group team);
|
||||
|
||||
Group findTeam(Person person);
|
||||
@Query("start person=(%person) match (boss)-[:boss]->(person) return boss")
|
||||
Person findBoss(@Param("person") Person person);
|
||||
|
||||
@Query("start team=(%d) match (team)-[:persons]->(member) return member")
|
||||
Page<Person> findAllTeamMembersPaged(Pageable page, Group team);
|
||||
@Query("start team=(%d) match (team)-[:persons]->(member) return member")
|
||||
Iterable<Person> findAllTeamMembersSorted(Group team, Sort sort);
|
||||
Group findTeam(@Param("person") Person person);
|
||||
|
||||
@Query("start team=(%team) match (team)-[:persons]->(member) return member")
|
||||
Page<Person> findAllTeamMembersPaged(@Param("team") Group team, Pageable page);
|
||||
@Query("start team=(%team) match (team)-[:persons]->(member) return member")
|
||||
Iterable<Person> findAllTeamMembersSorted(@Param("team") Group team, Sort sort);
|
||||
}
|
||||
|
||||
@@ -75,6 +75,12 @@ public class GraphRepositoryTest {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembers(testTeam.sdg);
|
||||
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil));
|
||||
}
|
||||
@Test
|
||||
@Transactional
|
||||
public void testFindIterableOfPersonWithQueryAnnotationAndGremlin() {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembersGremlin(testTeam.sdg);
|
||||
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
@@ -93,21 +99,21 @@ public class GraphRepositoryTest {
|
||||
@Transactional
|
||||
public void testFindPaged() {
|
||||
final PageRequest page = new PageRequest(0, 1, Sort.Direction.ASC, "member.name");
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(page, testTeam.sdg);
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,page);
|
||||
assertThat(teamMemberPage1, hasItem(testTeam.david));
|
||||
}
|
||||
@Test
|
||||
@Transactional
|
||||
public void testFindPagedDescending() {
|
||||
final PageRequest page = new PageRequest(0, 2, Sort.Direction.DESC, "member.name");
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(page, testTeam.sdg);
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,page);
|
||||
assertEquals(asList(testTeam.michael, testTeam.emil), asCollection(teamMemberPage1));
|
||||
assertThat(teamMemberPage1.isFirstPage(), is(true));
|
||||
}
|
||||
@Test
|
||||
@Transactional
|
||||
public void testFindPagedNull() {
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(null, testTeam.sdg);
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,null);
|
||||
assertEquals(asList(testTeam.michael, testTeam.emil,testTeam.david), asCollection(teamMemberPage1));
|
||||
assertThat(teamMemberPage1.isFirstPage(), is(true));
|
||||
assertThat(teamMemberPage1.isLastPage(), is(false));
|
||||
|
||||
@@ -21,15 +21,15 @@ 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.annotation.QueryType;
|
||||
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 +59,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;
|
||||
@@ -69,7 +69,7 @@ public class QueryEngineTest {
|
||||
graphDatabase = createGraphDatabase();
|
||||
testTeam = new TestTeam();
|
||||
testTeam.createSDGTeam();
|
||||
queryEngine = graphDatabase.queryEngineFor(QueryEngine.Type.Cypher);
|
||||
queryEngine = graphDatabase.queryEngineFor(QueryType.Cypher);
|
||||
michael = testTeam.michael;
|
||||
}
|
||||
|
||||
@@ -87,31 +87,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 +123,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 +142,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);
|
||||
}
|
||||
|
||||
@@ -1,121 +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 org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.matchers.IsCollectionContaining;
|
||||
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;
|
||||
import org.springframework.data.neo4j.support.TestTeam;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 13.06.11
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
|
||||
@Transactional
|
||||
public class QueryExecutorTest {
|
||||
@Autowired
|
||||
GraphDatabaseContext graphDatabaseContext;
|
||||
private QueryExecutor queryExecutor;
|
||||
private TestTeam testTeam;
|
||||
private Person michael;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
testTeam = new TestTeam();
|
||||
testTeam.createSDGTeam();
|
||||
queryExecutor = new QueryExecutor(graphDatabaseContext);
|
||||
michael = testTeam.michael;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testQueryList() throws Exception {
|
||||
final String queryString = String.format("start person=(%d,%d) return person.name, person.age", michael.getNodeId(), testTeam.david.getNodeId());
|
||||
final Collection<Map<String,Object>> result = IteratorUtil.asCollection(queryExecutor.queryForList(queryString));
|
||||
|
||||
assertEquals(asList(testTeam.simpleRowFor(michael,"person"),testTeam.simpleRowFor(testTeam.david,"person")),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(queryExecutor.query(queryString, Person.class));
|
||||
|
||||
assertEquals(asList(testTeam.emil),result);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryOtherTeamMembers() throws Exception {
|
||||
final String queryString = String.format("start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member", michael.getNodeId());
|
||||
System.out.println("testTeam = " + testTeam.sdg.getPersons());
|
||||
final Collection<Person> result = IteratorUtil.asCollection(queryExecutor.query(queryString, Person.class));
|
||||
|
||||
assertThat(result, IsCollectionContaining.hasItems(testTeam.david, testTeam.emil));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryAllTeamMembersByTeam() throws Exception {
|
||||
final String queryString = String.format("start team=(Group,name,\"%s\") match (team)-[:persons]->(member) return member", testTeam.sdg.getName());
|
||||
final Collection<Person> result = IteratorUtil.asCollection(queryExecutor.query(queryString, Person.class));
|
||||
|
||||
assertThat(result, IsCollectionContaining.hasItems(testTeam.david,testTeam.michael));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForObjectAsGroup() throws Exception {
|
||||
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:persons]- (team) return team", michael.getName());
|
||||
final Group result = queryExecutor.queryForObject(queryString, Group.class);
|
||||
|
||||
assertEquals(testTeam.sdg,result);
|
||||
}
|
||||
@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 = queryExecutor.queryForObject(queryString, String.class);
|
||||
|
||||
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 = queryExecutor.queryForObject(queryString, Personality.class);
|
||||
|
||||
assertEquals(michael.getPersonality(),result);
|
||||
}
|
||||
}
|
||||
@@ -1,151 +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 org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.matchers.IsCollectionContaining;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.helpers.collection.IteratorUtil;
|
||||
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.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.support.DelegatingGraphDatabase;
|
||||
import org.springframework.data.neo4j.support.GraphDatabaseContext;
|
||||
import org.springframework.data.neo4j.support.TestTeam;
|
||||
import org.springframework.data.neo4j.support.node.Neo4jHelper;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.transaction.BeforeTransaction;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 13.06.11
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
|
||||
@Transactional
|
||||
public class QueryOperationsTest {
|
||||
@Autowired
|
||||
protected ConversionService conversionService;
|
||||
@Autowired
|
||||
private GraphDatabaseContext graphDatabaseContext;
|
||||
private QueryOperations queryOperations;
|
||||
private TestTeam testTeam;
|
||||
private Person michael;
|
||||
private GraphDatabase graphDatabase;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
graphDatabase = createGraphDatabase();
|
||||
testTeam = new TestTeam();
|
||||
testTeam.createSDGTeam();
|
||||
queryOperations = new DefaultQueryOperations(graphDatabase.queryEngineFor(QueryEngine.Type.Cypher));
|
||||
michael = testTeam.michael;
|
||||
}
|
||||
|
||||
@BeforeTransaction
|
||||
public void cleanDb() {
|
||||
Neo4jHelper.cleanDb(graphDatabaseContext);
|
||||
}
|
||||
|
||||
protected GraphDatabase createGraphDatabase() throws Exception {
|
||||
final DelegatingGraphDatabase graphDatabase = new DelegatingGraphDatabase(graphDatabaseContext.getGraphDatabaseService());
|
||||
graphDatabase.setConversionService(conversionService);
|
||||
return graphDatabase;
|
||||
}
|
||||
|
||||
@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(queryOperations.queryForList(queryString));
|
||||
|
||||
assertEquals(asList(testTeam.simpleRowFor(michael,"person"),testTeam.simpleRowFor(testTeam.david,"person")),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<Node> result = IteratorUtil.asCollection(queryOperations.query(queryString, Node.class));
|
||||
|
||||
assertEquals(asList(nodeFor(testTeam.emil)),result);
|
||||
|
||||
}
|
||||
|
||||
private Node nodeFor(final NodeBacked entity) {
|
||||
return entity.getPersistentState();
|
||||
}
|
||||
private long idFor(final NodeBacked entity) {
|
||||
return entity.getNodeId();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryOtherTeamMembers() throws Exception {
|
||||
final String queryString = String.format("start person=(%d) match (person)<-[:persons]-(team)-[:persons]->(member) return member", idFor(michael));
|
||||
System.out.println("testTeam = " + testTeam.sdg.getPersons());
|
||||
final Collection<Node> result = IteratorUtil.asCollection(queryOperations.query(queryString, Node.class));
|
||||
|
||||
assertThat(result, IsCollectionContaining.hasItems(nodeFor(testTeam.david), nodeFor(testTeam.emil)));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryAllTeamMembersByTeam() throws Exception {
|
||||
final String queryString = String.format("start team=(Group,name,\"%s\") match (team)-[:persons]->(member) return member", testTeam.sdg.getName());
|
||||
final Collection<Node> result = IteratorUtil.asCollection(queryOperations.query(queryString, Node.class));
|
||||
|
||||
assertThat(result, IsCollectionContaining.hasItems(nodeFor(testTeam.david),nodeFor(testTeam.michael)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryForObjectAsGroup() throws Exception {
|
||||
final String queryString = String.format("start person=(name_index,name,\"%s\") match (person) <-[:persons]- (team) return team", michael.getName());
|
||||
final Node result = queryOperations.queryForObject(queryString, Node.class);
|
||||
|
||||
assertEquals(nodeFor(testTeam.sdg),result);
|
||||
}
|
||||
@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 = queryOperations.queryForObject(queryString, String.class);
|
||||
|
||||
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 = queryOperations.queryForObject(queryString, Personality.class);
|
||||
|
||||
assertEquals(michael.getPersonality(),result);
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ public class Neo4jTemplateApiTest {
|
||||
|
||||
@Test
|
||||
public void testCreateNode() throws Exception {
|
||||
Node node=template.createNode();
|
||||
Node node= template.createNode(null);
|
||||
assertNotNull("created node",node);
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
@@ -295,7 +305,7 @@ public class Neo4jTemplateApiTest {
|
||||
|
||||
@Test
|
||||
public void shouldCreateRelationshipWithProperty() throws Exception {
|
||||
Relationship relationship = template.createRelationship(referenceNode, node1, HAS,map("name","rel2"));
|
||||
Relationship relationship = template.createRelationship(referenceNode, node1, HAS,map("name", "rel2"));
|
||||
assertNotNull(relationship);
|
||||
assertEquals(referenceNode, relationship.getStartNode());
|
||||
assertEquals(node1,relationship.getEndNode());
|
||||
|
||||
@@ -1 +1 @@
|
||||
Person.findTeam=start p=(%d) match (p)<-[:persons]-(group) return group
|
||||
Person.findTeam=start p=(%person) match (p)<-[:persons]-(group) return group
|
||||
@@ -150,7 +150,7 @@
|
||||
<property name="namedQueries">
|
||||
<bean class="org.springframework.data.repository.core.support.PropertiesBasedNamedQueries">
|
||||
<constructor-arg>
|
||||
<props><prop key="Person.findTeam">start p=(%d) match (p)<-[:persons]-(group) return group</prop></props>
|
||||
<props><prop key="Person.findTeam">start p=(%person) match (p)<-[:persons]-(group) return group</prop></props>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</property>
|
||||
|
||||
@@ -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
|
||||
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
|
||||
Import-Package:
|
||||
net.sf.cglib.proxy;version="[2.2.0,3.0.0)",
|
||||
net.sf.cglib.core;version="[2.2.0,3.0.0)",
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<book xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<!-- TODO
|
||||
* Gremlin @Query on fields
|
||||
* Gremlin @Query on repository methods
|
||||
* variables in gremlin queries
|
||||
* variables in cypher queries
|
||||
* New Template Query API
|
||||
* Spring Roo Addon, Intro, Example Session
|
||||
* Update tutorial to include some of the new features (cypher, gremlin)
|
||||
*
|
||||
* more on Gremlin / Cypher over REST
|
||||
-->
|
||||
<bookinfo>
|
||||
<title>Good Relationships</title>
|
||||
<subtitle>The Spring Data Graph Guide Book</subtitle>
|
||||
@@ -23,14 +34,6 @@
|
||||
<firstname>David</firstname>
|
||||
<surname>Montag</surname>
|
||||
</author>
|
||||
<author>
|
||||
<firstname>Mark</firstname>
|
||||
<surname>Pollack</surname>
|
||||
</author>
|
||||
<author>
|
||||
<firstname>Thomas</firstname>
|
||||
<surname>Risberg</surname>
|
||||
</author>
|
||||
</authorgroup>
|
||||
|
||||
<legalnotice>
|
||||
@@ -40,6 +43,9 @@
|
||||
further provided that each copy contains this Copyright Notice, whether
|
||||
distributed in print or electronically.
|
||||
</para>
|
||||
<para>
|
||||
Copyright 2010-2011 Neo Technology
|
||||
</para>
|
||||
</legalnotice>
|
||||
|
||||
</bookinfo>
|
||||
|
||||
@@ -3,44 +3,76 @@
|
||||
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
|
||||
<preface>
|
||||
<title>About this guide book</title>
|
||||
<para>
|
||||
Welcome to the Spring Data Graph Guide Book. Thank you for taking the time to get an in depth look
|
||||
into <ulink url="https://github.com/SpringSource/spring-data-graph">Spring Data Graph</ulink>.
|
||||
This project is part of the <ulink url="http://springsource.org/spring-data">Spring Data project</ulink>,
|
||||
which brings the convenient programming model of the Spring Framework to modern NOSQL databases.
|
||||
Spring Data Graph, as the name alludes to, aims to provide support for graph databases. It currently
|
||||
supports <ulink url="http://neo4j.org">Neo4j</ulink>.
|
||||
</para>
|
||||
<para>
|
||||
It was written by developers for developers. Hopefully we've created a document that is well received
|
||||
by our peers.
|
||||
</para>
|
||||
<para>
|
||||
If you have any feedback on Spring Data Graph or this book, please provide it via the
|
||||
<ulink url="https://jira.springsource.org/browse/DATAGRAPH">SpringSource JIRA</ulink>, the
|
||||
<ulink url="http://forum.springsource.org/forumdisplay.php?f=80">SpringSource NOSQL Forum</ulink>,
|
||||
<ulink url="https://github.com/SpringSource/spring-data-graph/issues">github comments or issues</ulink>,
|
||||
or the <ulink url="http://neo4j.org/community/list/">Neo4j mailing list</ulink>.
|
||||
</para>
|
||||
<para>
|
||||
This book is presented as a <ulink url="http://martinfowler.com/bliki/DuplexBook.html">duplex book</ulink>,
|
||||
a term coined by Martin Fowler. A duplex book consists of at least two parts. The first part is an easily
|
||||
accessible tutorial that gives the reader an overview of the topics contained in the book. It contains lots
|
||||
of examples and discussion topics. This part of the book is highly suited for cover-to-cover reading.
|
||||
</para>
|
||||
<para>
|
||||
We chose a tutorial describing the creation of a web application that allows movie enthusiasts to
|
||||
find their favorite movies, rate them, connect with fellow movie geeks, and enjoy social features such as
|
||||
recommendations. The application is running on Neo4j using Spring Data Graph and the well-known Spring
|
||||
Web Stack.
|
||||
</para>
|
||||
<para>
|
||||
The second part of the book is the classic reference documentation, containing detailed information about
|
||||
the library. It discusses the programming model, the underlying assumptions, and internals, as well as the
|
||||
APIs for the object-graph mapping. The reference documentation is typically used to look up concrete bits of
|
||||
information, or to drill down into certain topics. For hackers wanting to really delve into Spring Data
|
||||
Graph, it can of course also be read cover-to-cover.
|
||||
</para>
|
||||
<section>
|
||||
<title>The Spring Data Graph Project</title>
|
||||
<para>
|
||||
Welcome to the Spring Data Graph Guide Book. Thank you for taking the time to get an in depth look
|
||||
into <ulink url="https://github.com/SpringSource/spring-data-graph">Spring Data Graph</ulink>.
|
||||
This project is part of the <ulink url="http://springsource.org/spring-data">Spring Data project</ulink>,
|
||||
which brings the convenient programming model of the Spring Framework to modern NOSQL databases.
|
||||
Spring Data Graph, as the name alludes to, aims to provide support for graph databases. It currently
|
||||
supports <ulink url="http://neo4j.org">Neo4j</ulink>.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Feedback</title>
|
||||
<para>
|
||||
It was written by developers for developers. Hopefully we've created a guide that is well received
|
||||
by our peers.
|
||||
</para>
|
||||
<para>
|
||||
If you have any feedback on Spring Data Graph or this book, please provide it via the
|
||||
<ulink url="https://jira.springsource.org/browse/DATAGRAPH">SpringSource JIRA</ulink>, the
|
||||
<ulink url="http://forum.springsource.org/forumdisplay.php?f=80">SpringSource NOSQL Forum</ulink>,
|
||||
<ulink url="https://github.com/SpringSource/spring-data-graph/issues">github comments or issues</ulink>,
|
||||
or the <ulink url="http://neo4j.org/community/list/">Neo4j mailing list</ulink>.
|
||||
</para>
|
||||
</section>
|
||||
<section><title>Format of the Book</title>
|
||||
<para>
|
||||
This book is presented as a <ulink url="http://martinfowler.com/bliki/DuplexBook.html">duplex book</ulink>,
|
||||
a term coined by Martin Fowler. A duplex book consists of at least two parts. The first part is an easily
|
||||
accessible tutorial that gives the reader an overview of the topics contained in the book. It contains lots
|
||||
of examples and discussion topics. This part of the book is highly suited for cover-to-cover reading.
|
||||
</para>
|
||||
<para>
|
||||
We chose a tutorial describing the creation of a web application that allows movie enthusiasts to
|
||||
find their favorite movies, rate them, connect with fellow movie geeks, and enjoy social features such as
|
||||
recommendations. The application is running on Neo4j using Spring Data Graph and the well-known Spring
|
||||
Web Stack.
|
||||
</para>
|
||||
<para>
|
||||
The second part of the book is the classic reference documentation, containing detailed information about
|
||||
the library. It discusses the programming model, the underlying assumptions, and internals, as well as the
|
||||
APIs for the object-graph mapping. The reference documentation is typically used to look up concrete bits of
|
||||
information, or to drill down into certain topics. For hackers wanting to really delve into Spring Data
|
||||
Graph, it can of course also be read cover-to-cover.
|
||||
</para>
|
||||
</section>
|
||||
<section><title>Acknowledgements</title>
|
||||
<para>
|
||||
We would like to thank everyone who contributed to this book, especially Mark Pollack and Thomas Risberg,
|
||||
the leads of the Spring Data Project, who helped a lot during the development of the library as well as sharing
|
||||
great feedback about the book. Also Oliver Gierke, our local German VMWare/SpringSource engineer, who invested a
|
||||
lot of time discussing various aspects of the library as well as providing the superb foundations for the Spring
|
||||
Data Repositories. We tortured Andy Clement, the AspectJ project lead, with many questions and issues around our
|
||||
advanced AspectJ usage which caused some headaches. He always quickly solved our issues and gave us excellent
|
||||
answers.
|
||||
</para>
|
||||
<para>
|
||||
We also appreciate very much the foresight of Rod Johnson and Emil Eifrem to initiate the project, and now
|
||||
also providing great forewords. Their leadership inspired collaboration between the engineering teams at
|
||||
SpringSource and Neo Technology, a tremendous help during the making of Spring Data Graph.
|
||||
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Last but not least we thank our vibrant community, both in the Spring Forums as well as on the Neo4j
|
||||
Mailing list and on many other places on the internet for giving us feedback, reporting issues and suggesting
|
||||
improvements. Without that important feedback we wouldn't be where we are today.
|
||||
|
||||
</para>
|
||||
</section>
|
||||
<para>
|
||||
Enjoy the book!
|
||||
</para>
|
||||
|
||||
@@ -88,8 +88,8 @@ public void foo( @Context WorldRepository repo ) {
|
||||
</note>
|
||||
<para>
|
||||
Please also keep in mind that performing graph operations via the REST-API is about one order of
|
||||
magnitude slower than location operations. Try to use the Neo4j-Query-Language or
|
||||
server-side traversals whenever possible (<code>RestTraversal</code>) for retrieving large sets of data.
|
||||
magnitude slower than location operations. Try to use the Neo4j Cypher query language,
|
||||
server-side traversals (<code>RestTraversal</code>) or Gremlin expressions whenever possible for retrieving large sets of data.
|
||||
Future versions of Spring Data Graph will use the more performant batching as well as a binary protocol.
|
||||
</para>
|
||||
<para>
|
||||
@@ -124,5 +124,8 @@ public void foo( @Context WorldRepository repo ) {
|
||||
to the remote instance. (e.g. <code>queryEngineFor(), index() and createTraversalDescription()</code>).
|
||||
Please use those methods when interacting with a remote server for optimal performance.
|
||||
</para>
|
||||
<para>
|
||||
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -150,6 +150,91 @@ try {
|
||||
for (Node foundNode : nodeIndex.get("property","value")) {
|
||||
// found node
|
||||
}
|
||||
]]></programlisting>
|
||||
</example>
|
||||
</section>
|
||||
<section>
|
||||
<title>Querying with Cypher</title>
|
||||
<para>
|
||||
With version 1.4.M04 Neo4j introduced a textual query language called
|
||||
<ulink url="http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html">"Cypher"</ulink> which draws from many
|
||||
sources. From graph matching like in SPARQL, some keywords and query structure that reminds of SQL and
|
||||
some iconic representation. A screencast presenting cypher queries on the cineasts.net dataset is available
|
||||
at <ulink url="http://video.neo4j.org/U2Y/introduction-to-cypher">video.neo4j.org</ulink>. Cypher was written
|
||||
in Scala to leverage the high expressiveness for lazy sequence operations of the language and the great
|
||||
parser combinator library.
|
||||
</para>
|
||||
<para>
|
||||
Cypher queries always begin with a <code>start</code> set of nodes. Those can be either expressed by their
|
||||
id's or by a index lookup expression. Those start-nodes are then related to other nodes in the
|
||||
<code>match</code> clause to other nodes. Start and match clause can introduce new identifiers for nodes and
|
||||
relationships. In the <code>where</code> clause additional filtering of the result set is applied by evaluating
|
||||
boolean expressions. The <code>return</code> clause defines which part of the query result will be available.
|
||||
Aggregation also happens in the return clause by using aggregation functions on some of the values.
|
||||
Sorting can happen in the <code>order by</code> clause and the <code>skip</code> and <code>limit</code> parts
|
||||
restrict the result set to a certain window.
|
||||
</para>
|
||||
<para>
|
||||
Cypher can be executed on an embedded graph db using <code>ExecutionEngine</code> and
|
||||
<code>CypherParser</code>. This is encapsulated in Spring Data Graph with
|
||||
<code>CypherQueryEngine</code>. The Neo4j-REST-Server comes with a Cypher-Plugin that is accessible remotely and is
|
||||
available in the Spring Data Graph REST-Binding.
|
||||
</para>
|
||||
<example>
|
||||
<title>Cypher Examples on the Cineasts.net Dataset</title>
|
||||
<programlisting><![CDATA[
|
||||
// Actors of Forrest Gump:
|
||||
start movie=(Movie,id,'13') match (movie)<-[:ACTS_IN]-(actor)
|
||||
return actor.name, actor.birthplace?
|
||||
|
||||
// User-Ratings:
|
||||
start user=(User,login,'micha') match (user)-[r,:RATED]->(movie) where r.stars > 3
|
||||
return movie.title, r.stars, r.comment
|
||||
|
||||
// Mutual Friend recommendations:
|
||||
start user=(User,login,'micha') match (user)-[:FRIEND]-(friend)-[r,:RATED]->(movie) where r.stars > 3
|
||||
return friend.name, movie.title, r.stars, r.comment?
|
||||
|
||||
// Movie suggestions based on a movie:
|
||||
start movie=(Movie,id,'13') match (movie)<-[:ACTS_IN]-()-[:ACTS_IN]->(suggestion)
|
||||
return suggestion.title, count(*) order by count(*) desc limit 5
|
||||
|
||||
// Co-Actors, sorted by count and name of Lucy Liu
|
||||
start lucy=(1000) match (lucy)-[:ACTS_IN]->(movie)<-[:ACTS_IN]-(co_actor)
|
||||
return count(*), co_actor.name order by count(*) desc,co_actor.name limit 20
|
||||
|
||||
// recommendations including counts, grouping and sorting
|
||||
start user=(User,login,'micha') match (user)-[:FRIEND]-(friend)-[r,:RATED]->(movie)
|
||||
return movie.title, AVG(r.stars), count(*) order by AVG(r.stars) desc, count(*) desc
|
||||
]]></programlisting>
|
||||
</example>
|
||||
</section>
|
||||
<section>
|
||||
<title>Gremlin a Graph Traversal DSL</title>
|
||||
<para>
|
||||
Gremlin is an expressive Groovy DSL developed by <ulink url="http://markorodriguez.com">Marko Rodriguez</ulink>
|
||||
as part of the <ulink url="http://tinkerpop.com">tinkerpop</ulink> stack. It builds on top of a pipe implementation
|
||||
(Blueprints Pipes) that uses connected operations to traverse a graph. Gremlin has a concise syntax but is
|
||||
turing complete.
|
||||
</para>
|
||||
<para>Gremlin can be executed by including the tinkerpop and blueprints dependencies and then requesting a <code>ScriptEngine</code>
|
||||
of type "gremlin" from the <code>javax.Script*</code> facilities. In Spring Data Graph this is encapsulated in
|
||||
<code>GremlinQueryEngine</code>. The Neo4j-REST-Server also comes with a Gremlin-Plugin that is accessible remotely and is
|
||||
available in the Spring Data Graph REST-Binding.
|
||||
</para>
|
||||
<example>
|
||||
<title>Sample Gremlin Queries</title>
|
||||
<programlisting><![CDATA[
|
||||
// Vertex with id 1
|
||||
v = g.v(1)
|
||||
|
||||
// determine the name of the vertices that vertex 1 knows and that are older than 30 years of age
|
||||
v.outE{it.label=='knows'}.inV{it.age > 30}.name
|
||||
|
||||
// calculate basic collaborative filtering for vertex 1
|
||||
m = [:]
|
||||
g.v(1).out('likes').in('likes').out('likes').groupCount(m)
|
||||
m.sort{a,b -> a.value <=> b.value}
|
||||
]]></programlisting>
|
||||
</example>
|
||||
</section>
|
||||
|
||||
@@ -48,9 +48,18 @@
|
||||
The current version that does not show incorrect errors is AspectJ 1.6.12.M1 (included in STS 2.7.0.M2), previous versions are reported
|
||||
to mislead the user.
|
||||
</para>
|
||||
<note>
|
||||
<para>
|
||||
There might be some issues with the eclipse maven plugin not adding AspectJ files correctly to the build path. If you encounter issues, please try the following:
|
||||
|
||||
Try editing the build path to <code>include **/*.aj</code> for the spring-data-neo4j project.
|
||||
You can do this by selecting "Build Path -> Configure Build Path ..." from the Package Explorer.
|
||||
Then for the <code>spring-data-neo4j/src/main/java</code> add <code>**/*.aj</code> to the Included path.
|
||||
</para>
|
||||
</note>
|
||||
<para>
|
||||
The AspectJ support in IntelliJ IDEA lacks some of the features. JetBrains is working on improving
|
||||
the situation in their upcoming 10.5 release of their popular IDE. Their latest work is available
|
||||
the situation in their upcoming 11 release of their popular IDE. Their latest work is available
|
||||
under their early access program (EAP). Building the project with the AspectJ compiler
|
||||
<code>ajc</code> works in IDEA (Options -> Compiler -> Java Compiler should show ajc). Make sure to
|
||||
give the compiler at least 512 MB of RAM.
|
||||
|
||||
@@ -114,19 +114,19 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Executes the given query, replacing the first %d with the node-id and returning the results converted to the target type.</term>
|
||||
<term>Executes the given query, replacing <code>%start</code> with the node-id and returning the results converted to the target type.</term>
|
||||
<listitem>
|
||||
<para><code><T> Iterable<T> NodeBacked.findAllByQuery(final String query, final Class<T> targetType)</code></para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Executes the given query, replacing the first %d with the node-id and returning the original result, but with nodes and relationships replaced by their appropriate entities.</term>
|
||||
<term>Executes the given query, replacing <code>%start</code> with the node-id and returning the original result, but with nodes and relationships replaced by their appropriate entities.</term>
|
||||
<listitem>
|
||||
<para><code>Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query)</code></para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>Executes the given query, replacing the first %d with the node-id and returns a single result converted to the target type.</term>
|
||||
<term>Executes the given query, replacing <code>%start</code> with the node-id and returns a single result converted to the target type.</term>
|
||||
<listitem>
|
||||
<para><code><T> T NodeBacked.findByQuery(final String query, final Class<T> targetType)</code></para>
|
||||
</listitem>
|
||||
|
||||
@@ -72,28 +72,33 @@ public class Movie {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>@GraphQuery: fields as query result views</title>
|
||||
<title>@Query: fields as query result views</title>
|
||||
<para>
|
||||
The <code>@GraphQuery</code> annotation leverages the delegation infrastructure used by the
|
||||
The <code>@Query</code> annotation leverages the delegation infrastructure used by the
|
||||
Spring Data Graph aspects. It provides dynamic fields which, when accessed, return the values
|
||||
selected by the provided query language expression. The provided query must contain a placeholder
|
||||
for the id of the current entity <code>start n=(%d) match n-[:FRIEND]->friend return friend</code>
|
||||
As graph queries can return variable number of entities the annotation can be put onto fields
|
||||
with a single value, an Iterable of a type or an Iterable of <code>Map<String,Object></code>.
|
||||
The class of the resulting node entities must right now provided with the <code>elementClass</code> attribute.
|
||||
Additional parameters are added to the query with Java's String.format substitution.
|
||||
selected by the provided query language expression. The provided query must contain a placeholder named <code>%start</code>
|
||||
for the id of the current entity. For instance <code>start n=(%start) match n-[:FRIEND]->friend return friend</code>.
|
||||
Graph queries can return variable number of entities. That's why annotation can be put onto fields
|
||||
with a single value, an Iterable of a concrete type or an Iterable of <code>Map<String,Object></code>.
|
||||
Additional parameters are taken from the params attribute of the <code>@Query</code> annotation.
|
||||
The tuples form key-value pairs that are provided to the query at execution time.
|
||||
</para>
|
||||
<example>
|
||||
<title>@GraphQuery from a node entity</title>
|
||||
<title>@Graph on a node entity field</title>
|
||||
<programlisting language="java"><![CDATA[@NodeEntity
|
||||
public class Group {
|
||||
@GraphQuery(value = "start n=(%d) match (n)-[:%s]->(friend) return friend",
|
||||
elementClass = Person.class, params = "FRIEND")
|
||||
@Query(value = "start n=(%start) match (n)-[:%relType]->(friend) return friend",
|
||||
params = {"relType", "FRIEND"})
|
||||
private Iterable<Person> friends;
|
||||
}
|
||||
]]></programlisting>
|
||||
|
||||
</example>
|
||||
<para>
|
||||
<note>
|
||||
Please note that this annotation can also be used on repository methods.
|
||||
</note>
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>@GraphTraversal: fields as traversal result views</title>
|
||||
|
||||
@@ -132,13 +132,16 @@
|
||||
<section>
|
||||
<title>Annotated Queries</title>
|
||||
<para>
|
||||
Queries for the graph-query language cypher can be supplied with the <code>@GraphQuery</code> annotation.
|
||||
That means every method annotated with <code>@GraphQuery("start n=(%d) match (n)-->(m) return m")</code>
|
||||
will use the query string. The String-format parameters are replaced by the actual method parameters in order,
|
||||
whereby Node and Relationship-Entities are resolved to their respective id's and all other parameters are
|
||||
Queries for the cypher graph-query language can be supplied with the <code>@Query</code> annotation.
|
||||
That means every method annotated with <code>@Query("start n=(%node) match (n)-->(m) return m")</code>
|
||||
will use the query string. The named parameter <code>%node</code> will be replaced by the actual method parameters.
|
||||
Node and Relationship-Entities are resolved to their respective id's and all other parameters are
|
||||
replaced directly (i.e. Strings, Longs, etc). There is special support for the <code>Sort</code> and <code>Pageable</code>
|
||||
parameters from Spring Data Commons, which are supported to add programmatic paging and sorting (alternatively
|
||||
static paging and sorting can be supplied in the query string itself).
|
||||
|
||||
For using the named parameters you have to either annotate the parameters of the method with the
|
||||
<code>@Param("node")</code> annotation or enable debug symbols.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
@@ -146,8 +149,9 @@
|
||||
<title>Named Queries</title>
|
||||
<para>Spring Data Graph also supports the notion of named queries which are externalized in property-config-files
|
||||
(<code>META-INF/graph-named-queries.properties</code>). Those files have the format:
|
||||
<code>Entity.finderName=query</code> (e.g. <code>Person.findBoss=start p=(%d) match (p)<-[:BOSS]-(boss) return boss</code>).
|
||||
Otherwise named queries support the same parameters as annotated queries.
|
||||
<code>Entity.finderName=query</code> (e.g. <code>Person.findBoss=start p=(%person) match (p)<-[:BOSS]-(boss) return boss</code>).
|
||||
Otherwise named queries support the same parameters as annotated queries. For using the named parameters you have to either
|
||||
annotate the parameters of the method with the <code>@Param("person")</code> annotation or enable debug symbols.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
|
||||
@@ -14,68 +14,89 @@
|
||||
</para>
|
||||
<para>
|
||||
There are methods (<code>createNode()</code> and <code>createRelationship()</code>) for creating nodes and
|
||||
relationships that automatically set provided properties and optionally index certain fields.
|
||||
relationships that automatically set provided properties.
|
||||
</para>
|
||||
<example>
|
||||
<title>Neo4j template</title>
|
||||
<programlisting language="java"><![CDATA[<![CDATA[import static org.springframework.data.neo4j.core.Property._;
|
||||
<programlisting language="java"><![CDATA[<![CDATA[import static org.neo4j.helpers.collection.MapUtil.map;
|
||||
|
||||
Neo4jOperations neo = new Neo4jTemplate(graphDatabaseService);
|
||||
|
||||
Node michael = neo.createNode(_("name","Michael"));
|
||||
Node mark = neo.createNode(_("name","Mark"));
|
||||
Node thomas = neo.createNode(_("name","Thomas"));
|
||||
Node michael = neo.createNode(map("name","Michael"));
|
||||
Node mark = neo.createNode(map("name","Mark"));
|
||||
Node thomas = neo.createNode(map("name","Thomas"));
|
||||
|
||||
neo.createRelationship(mark,thomas, WORKS_WITH, _("project","spring-data"));
|
||||
neo.createRelationship(mark,thomas, WORKS_WITH, map("project","spring-data"));
|
||||
|
||||
neo.index("devs",thomas, "name","Thomas");
|
||||
|
||||
assert "Mark".equals(neo.query("devs","name","Mark",new NodeNamePathMapper()));
|
||||
]]></programlisting>
|
||||
// Cypher
|
||||
assert "Mark".equals(neo.query("start p=(%person) match p<-[:WORKS_WITH]-other return other.name",
|
||||
map("person",thomas)).to(String.class).single());
|
||||
|
||||
// Gremlin
|
||||
assert thomas.equals(neo.execute("g.v(person).out('WORKS_WITH')",
|
||||
map("person",mark)).to(Node.class).single());
|
||||
|
||||
// Index lookup
|
||||
assert mark.equals(neo.lookup("devs","name","Mark").single());
|
||||
|
||||
// Index lookup with Result Converter
|
||||
assert "Mark".equals(neo.lookup("devs","name","Mark").to(String.class, new ResultConverter<PropertyContainer, String> {
|
||||
public String convert(PropertyContainer element, Class<String> type) {
|
||||
return (String) element.getProperty("name");
|
||||
}}));]]></programlisting>
|
||||
</example>
|
||||
</section>
|
||||
<section>
|
||||
<title>QueryResult</title>
|
||||
<para>
|
||||
All querying methods of the template return a uniform result type: <code>QueryResult<T></code>
|
||||
which is also an <code>Iterable<T></code>. The query result offers methods of converting each
|
||||
element to a target type <code>queryResult.to(Type.class)</code> optionally supplying a
|
||||
<code>ResultConverter<FROM,TO></code> which takes care of custom conversions. By default most
|
||||
query methods can already handle conversions from and to: Paths, Nodes, Relationship and GraphEntities
|
||||
as well as conversions backed by registered ConversionServices. A converted <code>QueryResult<FROM></code> is an
|
||||
<code>Iterable<TO></code>. QueryResults can be limited to a single value using the <code>queryResult.single()</code>
|
||||
method. It also offers support for a pure callback function using a <code>Handler<T></code>.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Indexing</title>
|
||||
<para>
|
||||
Adding nodes and relationships to an index is done with the <code>index()</code> method.
|
||||
</para>
|
||||
<para>
|
||||
The <code>query()</code> methods either take a field/value combination to look for exact matches in the
|
||||
index, or a Lucene query object or string to handle more complex queries. All <code>query()</code>
|
||||
methods provide <code>Path</code> results to a PathMapper.
|
||||
The <code>lookup()</code> methods either take a field/value combination to look for exact matches in the
|
||||
index, or a Lucene query object or string to handle more complex queries. All <code>lookup()</code>
|
||||
methods return a <code>QueryResult<PropertyContainer></code> to be used or transformed.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Graph traversal</title>
|
||||
<para>
|
||||
The traversal methods are at the core of graph operations. As such, they are fully supported in the
|
||||
<code>Neo4jTemplate</code>. The <code>traverseNext()</code> method traverses to the direct neighbors
|
||||
of the start node, filtering the relationships according to the parameters.
|
||||
</para>
|
||||
<para>
|
||||
The traversal methods are at the core of graph operations.
|
||||
The <code>traverse()</code> method covers the full traversal operation that takes a
|
||||
<code>TraversalDescription</code> (typically built with the <code>Traversal.description()</code>
|
||||
DSL) and runs it from the start node. Each path that is returned by the traversal is passed to the
|
||||
<code>PathMapper</code> to be converted into the desired type.
|
||||
DSL) and runs it from the given start node. <code>traverse</code> returns a <code>QueryResult<Path></code>
|
||||
to be used or transformed.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Path abstraction and PathMapper</title>
|
||||
<title>Cypher Queries</title>
|
||||
<para>
|
||||
For the querying operations Neo4jTemplate unifies the result with the <code>Path</code> abstraction that
|
||||
comes from Neo4j. Much like a result set, a path contains a chain of <code>nodes()</code> connected by
|
||||
<code>relationships()</code>, starting at a <code>startNode()</code> and ending at a
|
||||
<code>endNode()</code>. The <code>lastRelationship()</code> is also available separately. The
|
||||
<code>Path</code> abstraction also wraps results that contain just nodes or relationships.
|
||||
The <code>Neo4jTemplate</code> also allows execution of arbitrary Cypher queries. Via the <code>query</code>
|
||||
methods the statement and parameter-Map are provided. Cypher Queries return tabular results, so the
|
||||
<code>QueryResult<Map<String,Object>></code> contains the rows which can be either used as they are
|
||||
or converted as needed.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Gremlin Scripts</title>
|
||||
<para>
|
||||
Using implementations of <code>PathMapper<T></code> and <code>PathMapper.WithoutResult</code>
|
||||
(comparable with <code>RowMapper</code> and <code>RowCallbackHandler</code>), the paths can be converted
|
||||
to arbitrary Java objects.
|
||||
</para>
|
||||
<para>
|
||||
With <code>EntityPath</code> and <code>EntityMapper</code> there is also support for using
|
||||
node entities within the <code>Path</code> and <code>PathMapper</code> constructs.
|
||||
Gremlin Scripts can run with the <code>execute</code> method, which also takes the parameters that will be
|
||||
available as variables inside the script. The result of the executions is a generic
|
||||
<code>QueryResult<Object></code> fit for conversion or usage.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
@@ -88,4 +109,11 @@ assert "Mark".equals(neo.query("devs","name","Mark",new NodeNamePathMapper()));
|
||||
or the <code>TransactionTemplate</code>.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
<title>Neo4j REST Server</title>
|
||||
<para>If the template is configured to use a <code>RestGraphDatabase</code> the expensive operations
|
||||
like traversals and querying are executed efficiently on the server side by using the REST API to forward
|
||||
those calls. All the other template methods require single network operations.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
BIN
src/docbkx/resources/images/note.png
Normal file
BIN
src/docbkx/resources/images/note.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Reference in New Issue
Block a user