extracted QueryEngine, added REST Query Engine Support

This commit is contained in:
Michael Hunger
2011-06-24 11:41:58 +02:00
parent 3fb6e6e41b
commit 91a4936c0d
24 changed files with 729 additions and 113 deletions

View File

@@ -16,7 +16,7 @@
<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.BUILD-SNAPSHOT</data.commons.version>
<neo4j.version>1.4.M04</neo4j.version>
<neo4j.version>1.4-SNAPSHOT</neo4j.version>
<aspectj.version>1.6.12.M1</aspectj.version>
</properties>
<profiles>

View File

@@ -55,7 +55,7 @@
<artifactId>neo4j</artifactId>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<groupId>org.neo4j.server.plugin</groupId>
<artifactId>neo4j-cypher-plugin</artifactId>
<version>${neo4j.version}</version>
</dependency>

View File

@@ -22,10 +22,13 @@ import org.neo4j.graphdb.event.KernelEventHandler;
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.graph.core.GraphDatabase;
import org.springframework.data.graph.core.Property;
import org.springframework.data.graph.neo4j.rest.support.index.RestIndexManager;
import org.springframework.data.graph.neo4j.support.query.ConversionServiceQueryResultConverter;
import org.springframework.data.graph.neo4j.support.query.QueryEngine;
import org.springframework.data.graph.neo4j.support.query.QueryResultConverter;
import javax.ws.rs.core.Response.Status;
import java.net.URI;
@@ -35,6 +38,7 @@ public class RestGraphDatabase implements GraphDatabaseService, GraphDatabase {
private RestRequest restRequest;
private long propertyRefetchTimeInMillis = 1000;
private ConversionService conversionService;
public RestGraphDatabase( URI uri ) {
@@ -99,7 +103,18 @@ public class RestGraphDatabase implements GraphDatabaseService, GraphDatabase {
@Override
public QueryEngine queryEngineFor(QueryEngine.Type type) {
return new RestQueryEngine(restRequest);
return new RestQueryEngine(this, createResultConverter());
}
private ConversionServiceQueryResultConverter createResultConverter() {
if (conversionService==null) return null;
return new ConversionServiceQueryResultConverter(conversionService);
}
@Override
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public RestIndexManager index() {

View File

@@ -0,0 +1,141 @@
/**
* 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.graph.neo4j.rest.support;
import com.sun.jersey.api.client.ClientResponse;
import org.neo4j.helpers.collection.IterableWrapper;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.graph.neo4j.support.query.QueryEngine;
import org.springframework.data.graph.neo4j.support.query.QueryResultConverter;
import java.util.*;
/**
* @author mh
* @since 22.06.11
*/
public class RestQueryEngine implements QueryEngine {
private final RestRequest restRequest;
private final RestGraphDatabase restGraphDatabase;
private final QueryResultConverter resultConverter;
public RestQueryEngine(RestGraphDatabase restGraphDatabase, QueryResultConverter resultConverter) {
this.restGraphDatabase = restGraphDatabase;
this.resultConverter = resultConverter;
this.restRequest = restGraphDatabase.getRestRequest();
}
@Override
public Iterable<Map<String, Object>> query(String statement) {
return executeStatement(statement).getData();
}
private RestQueryResult executeStatement(String statement) {
final ClientResponse response = restRequest.get("ext/CypherPlugin/graphdb/execute_query", JsonHelper.createJsonFrom(Collections.singletonMap("query", statement)));
return new RestQueryResult(restRequest.toMap(response));
}
class RestQueryResult {
List<String> columns;
List<Map<String,Object>> data;
public RestQueryResult(Map<?, ?> result) {
columns= (List<String>) result.get("columns");
extractData(result);
}
private void extractData(Map<?, ?> result) {
List<List<?>> rows= (List<List<?>>) result.get("data");
data=new ArrayList<Map<String, Object>>(rows.size());
for (List<?> row : rows) {
data.add(mapRow(row));
}
}
private Map<String, Object> mapRow(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), convertValue(value));
}
return newRow;
}
private Object convertValue(Object value) {
final Object representationValue = convertFromRepresentation(value);
return resultConverter.convertValue(representationValue, null);
}
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;
}
public List<Map<String, Object>> getData() {
return data;
}
public List<String> getColumns() {
return columns;
}
public <T> T getSingleValue(Class<T> type) {
if (data.size()==0) throw new InvalidDataAccessResourceUsageException("Expected single result, got none");
if (data.size()!=1) throw new InvalidDataAccessResourceUsageException("Expected single result, got more than one");
return getSingleColumn(type).iterator().next();
}
public <T> Iterable<T> getSingleColumn(final Class<T> type) {
if (columns.size()==0) throw new InvalidDataAccessResourceUsageException("Expected single column, got none");
if (columns.size()!=1) throw new InvalidDataAccessResourceUsageException("Expected single column, got more than one");
final String firstColumn = columns.get(0);
return new IterableWrapper<T, Map<String,Object>>(data) {
@Override
protected T underlyingObjectToObject(Map<String,Object> row) {
return resultConverter.convertValue(row.get(firstColumn),type);
}
};
}
}
@Override
public <T> Iterable<T> query(String statement, Class<T> type) {
final RestQueryResult restQueryResult = executeStatement(statement);
return restQueryResult.getSingleColumn(type);
}
@Override
public <T> T queryForObject(String statement, Class<T> type) {
return executeStatement(statement).getSingleValue(type);
}
}

View File

@@ -79,6 +79,14 @@ public class RestRequest {
return builder( path ).get( ClientResponse.class );
}
public ClientResponse get( String path, String data ) {
Builder builder = builder(path);
if ( data != null ) {
builder = builder.entity( data, MediaType.APPLICATION_JSON_TYPE );
}
return builder.get(ClientResponse.class);
}
public ClientResponse delete( String path ) {
return builder( path ).delete( ClientResponse.class );
}

View File

@@ -50,7 +50,9 @@ public class RestNeo4jTemplateTest extends Neo4jTemplateTest
protected GraphDatabase createGraphDatabase() throws Exception
{
testHelper.cleanDb();
return testHelper.createGraphDatabase();
final GraphDatabase graphDatabase = testHelper.createGraphDatabase();
graphDatabase.setConversionService(conversionService);
return graphDatabase;
}
@Override

View File

@@ -0,0 +1,53 @@
package org.springframework.data.graph.neo4j.rest.support;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.graph.core.GraphDatabase;
import org.springframework.data.graph.neo4j.support.DelegatingGraphDatabase;
import org.springframework.data.graph.neo4j.support.GraphRepositoryTest;
import org.springframework.data.graph.neo4j.support.query.QueryEngineTest;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
/**
* @author mh
* @since 23.06.11
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml",
"classpath:RestTest-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class RestQueryEngineTest extends QueryEngineTest {
@Autowired
RestGraphDatabase restGraphDatabase;
@BeforeClass
public static void startDb() throws Exception {
RestTestBase.startDb();
}
@BeforeTransaction
public void cleanDb() {
RestTestBase.cleanDb();
}
@AfterClass
public static void shutdownDb() {
RestTestBase.shutdownDb();
}
@Override
protected GraphDatabase createGraphDatabase() throws Exception {
restGraphDatabase.setConversionService(conversionService);
return restGraphDatabase;
}
}

View File

@@ -22,6 +22,9 @@ import org.neo4j.graphdb.Relationship;
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.graph.neo4j.support.query.EmbeddedQueryEngine;
import org.springframework.data.graph.neo4j.support.query.QueryEngine;
public interface GraphDatabase {
@@ -91,4 +94,8 @@ public interface GraphDatabase {
* @return a TraversalDescription as starting point for defining a traversal
*/
TraversalDescription createTraversalDescription();
QueryEngine queryEngineFor(EmbeddedQueryEngine.Type type);
void setConversionService(ConversionService conversionService);
}

View File

@@ -22,8 +22,13 @@ import org.neo4j.graphdb.index.IndexManager;
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.graph.core.GraphDatabase;
import org.springframework.data.graph.core.Property;
import org.springframework.data.graph.neo4j.support.query.ConversionServiceQueryResultConverter;
import org.springframework.data.graph.neo4j.support.query.EmbeddedQueryEngine;
import org.springframework.data.graph.neo4j.support.query.QueryEngine;
import org.springframework.data.graph.neo4j.support.query.QueryResultConverter;
import java.util.Map;
@@ -34,14 +39,14 @@ import java.util.Map;
public class DelegatingGraphDatabase implements GraphDatabase {
protected GraphDatabaseService delegate;
private ConversionService conversionService;
public DelegatingGraphDatabase(final GraphDatabaseService delegate) {
this.delegate = delegate;
}
@Override
public Node getReferenceNode() {
return delegate.getReferenceNode();
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
@@ -122,7 +127,22 @@ public class DelegatingGraphDatabase implements GraphDatabase {
return Traversal.description();
}
@Override
public QueryEngine queryEngineFor(EmbeddedQueryEngine.Type type) {
return new EmbeddedQueryEngine(delegate, createResultConverter());
}
private ConversionServiceQueryResultConverter createResultConverter() {
if (conversionService == null) return null;
return new ConversionServiceQueryResultConverter(conversionService);
}
public void shutdown() {
delegate.shutdown();
}
@Override
public Node getReferenceNode() {
return delegate.getReferenceNode();
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.graph.neo4j.support.query;
import org.springframework.core.convert.ConversionService;
/**
* @author mh
* @since 22.06.11
*/
public class ConversionServiceQueryResultConverter implements QueryResultConverter {
private final ConversionService conversionService;
public ConversionServiceQueryResultConverter(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public <T> T convertValue(Object value, Class<T> type) {
if (type==null || type.isInstance(value)) return (T) value;
return conversionService.convert(value,type);
}
}

View File

@@ -0,0 +1,126 @@
/**
* 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.graph.neo4j.support.query;
import org.neo4j.cypher.SyntaxError;
import org.neo4j.cypher.commands.Query;
import org.neo4j.cypher.javacompat.CypherParser;
import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.helpers.collection.IterableWrapper;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class EmbeddedQueryEngine implements QueryEngine {
public EmbeddedQueryEngine(GraphDatabaseService graphDatabaseService) {
this(graphDatabaseService, QueryResultConverter.NO_OP_QUERY_RESULT_CONVERTER);
}
final ExecutionEngine executionEngine;
private QueryResultConverter resultConverter;
public EmbeddedQueryEngine(GraphDatabaseService graphDatabaseService, QueryResultConverter resultConverter) {
this.resultConverter = resultConverter != null ? resultConverter : QueryResultConverter.NO_OP_QUERY_RESULT_CONVERTER;
this.executionEngine = new ExecutionEngine(graphDatabaseService);
}
@Override
public Iterable<Map<String, Object>> query(String statement) {
try {
ExecutionResult result = parseAndExecuteQuery(statement);
return convertResult(result);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, e);
}
}
@Override
public <T> Iterable<T> query(String statement, Class<T> type) {
try {
ExecutionResult result = parseAndExecuteQuery(statement);
return convertResult(result, type);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement + " for type " + type, e);
}
}
@Override
public <T> T queryForObject(String statement, Class<T> type) {
try {
ExecutionResult result = parseAndExecuteQuery(statement);
final Iterable<T> convertedResult = convertResult(result, type);
return extractSingleResult(convertedResult);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement + " for type " + type, e);
}
}
private ExecutionResult parseAndExecuteQuery(String statement) {
try {
CypherParser parser = new CypherParser();
Query query = parser.parse(statement);
return executionEngine.execute(query);
} catch (SyntaxError syntaxError) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, syntaxError);
}
}
private <T> T extractSingleResult(Iterable<T> convertedResult) {
final Iterator<T> it = convertedResult.iterator();
if (!it.hasNext()) throw new InvalidDataAccessResourceUsageException("Expected single result, got none");
T value = it.hasNext() ? it.next() : null;
if (it.hasNext())
throw new InvalidDataAccessResourceUsageException("Expected single result, got more than one");
return value;
}
private <T> Iterable<T> convertResult(ExecutionResult result, final Class<T> type) {
final List<String> columns = result.columns();
if (columns.size() != 1)
throw new InvalidDataAccessResourceUsageException("Expected single column of results, got " + columns);
final String column = columns.get(0);
return new IterableWrapper<T, Map<String, Object>>(result) {
@Override
protected T underlyingObjectToObject(Map<String, Object> row) {
return resultConverter.convertValue(row.get(column), type);
}
};
}
private Iterable<Map<String, Object>> convertResult(Iterable<Map<String, Object>> result) {
return new IterableWrapper<Map<String, Object>, Map<String, Object>>(result) {
@Override
protected Map<String, Object> underlyingObjectToObject(Map<String, Object> row) {
Map<String,Object> newRow=new HashMap<String,Object>(row); // todo performance
for (Map.Entry<String, Object> entry : newRow.entrySet()) {
Object value = resultConverter.convertValue(entry.getValue(),null);
if (value != entry.getValue()) {
entry.setValue(value);
}
}
return row;
}
};
}
}

View File

@@ -0,0 +1,33 @@
/**
* 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.graph.neo4j.support.query;
import java.util.Map;
/**
* @author mh
* @since 22.06.11
*/
public interface QueryEngine {
Iterable<Map<String, Object>> query(String statement);
<T> Iterable<T> query(String statement, Class<T> type);
<T> T queryForObject(String statement, Class<T> type);
public enum Type { Cypher, Gremlin }
}

View File

@@ -16,22 +16,12 @@
package org.springframework.data.graph.neo4j.support.query;
import org.neo4j.cypher.SyntaxError;
import org.neo4j.cypher.commands.Query;
import org.neo4j.cypher.javacompat.CypherParser;
import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.helpers.collection.IterableWrapper;
import org.springframework.core.convert.ConversionService;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.graph.core.TypeRepresentationStrategy;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
@@ -39,83 +29,29 @@ import java.util.Map;
* @since 10.06.11
* todo limits
*/
public class QueryExecutor {
public class QueryExecutor implements QueryResultConverter {
private final TypeRepresentationStrategy nodeTypeRepresentationStrategy;
private final ExecutionEngine executionEngine;
private final TypeRepresentationStrategy relationshipTypeRepresentationStrategy;
private final ConversionService conversionService;
private final QueryEngine queryEngine;
public QueryExecutor(GraphDatabaseContext ctx) {
this.nodeTypeRepresentationStrategy = ctx.getNodeTypeRepresentationStrategy();
relationshipTypeRepresentationStrategy = ctx.getRelationshipTypeRepresentationStrategy();
conversionService = ctx.getConversionService();
this.executionEngine = new ExecutionEngine(ctx.getGraphDatabaseService());
queryEngine = new EmbeddedQueryEngine(ctx.getGraphDatabaseService(), this);
}
public Iterable<Map<String, Object>> query(String statement) {
try {
ExecutionResult result = parseAndExecuteQuery(statement);
return convertResult(result);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, e);
}
return queryEngine.query(statement);
}
public <T> Iterable<T> query(String statement, Class<T> type) {
try {
ExecutionResult result = parseAndExecuteQuery(statement);
return convertResult(result, type);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement + " for type " + type, e);
}
return queryEngine.query(statement, type);
}
public <T> T queryForObject(String statement, Class<T> type) {
try {
ExecutionResult result = parseAndExecuteQuery(statement);
final Iterable<T> convertedResult = convertResult(result, type);
return extractSingleResult(convertedResult);
} catch (Exception e) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement + " for type " + type, e);
}
}
private <T> T extractSingleResult(Iterable<T> convertedResult) {
final Iterator<T> it = convertedResult.iterator();
if (!it.hasNext()) throw new InvalidDataAccessResourceUsageException("Expected single result, got none");
T value = it.hasNext() ? it.next() : null;
if (it.hasNext())
throw new InvalidDataAccessResourceUsageException("Expected single result, got more than one");
return value;
}
private <T> Iterable<T> convertResult(ExecutionResult result, final Class<T> type) {
final List<String> columns = result.columns();
if (columns.size() != 1)
throw new InvalidDataAccessResourceUsageException("Expected single column of results, got " + columns);
final String column = columns.get(0);
return new IterableWrapper<T, Map<String, Object>>(result) {
@Override
protected T underlyingObjectToObject(Map<String, Object> row) {
return convertValue(row.get(column), type);
}
};
}
private Iterable<Map<String, Object>> convertResult(Iterable<Map<String, Object>> result) {
return new IterableWrapper<Map<String, Object>, Map<String, Object>>(result) {
@Override
protected Map<String, Object> underlyingObjectToObject(Map<String, Object> row) {
Map<String,Object> newRow=new HashMap<String,Object>(row); // todo performance
for (Map.Entry<String, Object> entry : newRow.entrySet()) {
Object value = convertValue(entry.getValue());
if (value != entry.getValue()) {
entry.setValue(value);
}
}
return row;
}
};
return queryEngine.queryForObject(statement,type);
}
private Object convertValue(Object value) {
@@ -127,23 +63,16 @@ public class QueryExecutor {
}
return value;
}
private <T> T convertValue(Object value,Class<T> type) {
public <T> T convertValue(Object value, Class<T> type) {
if (type == null) return (T) convertValue(value);
if (type.isInstance(value)) return type.cast(value);
if (value instanceof Node) {
return (T) nodeTypeRepresentationStrategy.createEntity((Node) value,type);
return (T) nodeTypeRepresentationStrategy.createEntity((Node) value, type);
}
if (value instanceof Relationship) {
return (T) relationshipTypeRepresentationStrategy.createEntity((Relationship) value,type);
}
return conversionService.convert(value,type);
}
private ExecutionResult parseAndExecuteQuery(String statement) {
try {
CypherParser parser = new CypherParser();
Query query = parser.parse(statement);
return executionEngine.execute(query);
} catch (SyntaxError syntaxError) {
throw new InvalidDataAccessResourceUsageException("Error executing statement " + statement, syntaxError);
return (T) relationshipTypeRepresentationStrategy.createEntity((Relationship) value, type);
}
return conversionService.convert(value, type);
}
}

View File

@@ -0,0 +1,32 @@
/**
* 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.graph.neo4j.support.query;
/**
* @author mh
* @since 22.06.11
*/
public interface QueryResultConverter {
<T> T convertValue(Object value, Class<T> type);
QueryResultConverter NO_OP_QUERY_RESULT_CONVERTER = new QueryResultConverter() {
@Override
public <T> T convertValue(Object value, Class<T> type) {
return (T) value;
}
};
}

View File

@@ -21,6 +21,10 @@ import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.helpers.collection.ClosableIterable;
import org.springframework.data.graph.neo4j.support.path.PathMapper;
import org.springframework.data.graph.core.Property;
import org.springframework.data.graph.neo4j.support.query.EmbeddedQueryEngine;
import org.springframework.data.graph.neo4j.support.query.QueryEngine;
import java.util.Map;
/**
* A template with convenience operations, exception translation and implicit transaction for modifying methods
@@ -163,4 +167,11 @@ public interface Neo4jOperations {
*/
<T extends PropertyContainer> T index(String indexName, T element, String field, Object value);
Iterable<Map<String, Object>> query(QueryEngine.Type engineType, String statement);
<T> Iterable<T> query(QueryEngine.Type engineType, String statement, Class<T> type);
<T> T queryForObject(QueryEngine.Type engineType, String statement, Class<T> type);
}

View File

@@ -31,6 +31,8 @@ import org.springframework.data.graph.neo4j.support.path.NodePath;
import org.springframework.data.graph.neo4j.support.path.PathMapper;
import org.springframework.data.graph.neo4j.support.path.PathMappingIterator;
import org.springframework.data.graph.neo4j.support.path.RelationshipPath;
import org.springframework.data.graph.neo4j.support.query.EmbeddedQueryEngine;
import org.springframework.data.graph.neo4j.support.query.QueryEngine;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
@@ -179,7 +181,7 @@ public class Neo4jTemplate implements Neo4jOperations {
@Override
public <T> ClosableIterable<T> query(String indexName, final PathMapper<T> pathMapper, String field, String value) {
notNull(field, "field", value, "value", pathMapper, "pathMapper",indexName,"indexName");
notNull(field, "field", value, "value", pathMapper, "pathMapper", indexName, "indexName");
try {
Index<? extends PropertyContainer> index = graphDatabase.getIndex(indexName);
if (Relationship.class.isAssignableFrom(index.getEntityType())) {
@@ -191,6 +193,10 @@ public class Neo4jTemplate implements Neo4jOperations {
}
}
private QueryEngine queryEngineFor(EmbeddedQueryEngine.Type type) {
return graphDatabase.queryEngineFor(type);
}
private <T> ClosableIterable<T> mapNodes(final IndexHits<Node> nodes, final PathMapper<T> pathMapper) {
assert nodes != null;
assert pathMapper != null;
@@ -314,4 +320,34 @@ public class Neo4jTemplate implements Neo4jOperations {
indexHits.close();
}
}
@Override
public Iterable<Map<String, Object>> query(QueryEngine.Type engineType, String statement) {
return queryEngineFor(engineType).query(statement);
}
@Override
public <T> Iterable<T> query(QueryEngine.Type engineType, String statement, Class<T> type) {
return queryEngineFor(engineType).query(statement,type);
}
@Override
public <T> T queryForObject(QueryEngine.Type engineType, String statement, Class<T> type) {
return queryEngineFor(engineType).queryForObject(statement,type);
}
}
/*
conversion for all query methods
-> target type
-> target type + mapper
-> no conversion (or auto-conversion) -> default mapper
source Path Node Relationship Primitive, DomainObject
Path -> Path, NodePath, RelationshipPath X lastNode()
Node -> endNode(), node, Mapper X NodeEntity
Relationship lastRel(), Mapper relationship X RelEntity
primitive X X X primitive
Map<String,Obj> Mapper
*/

View File

@@ -18,7 +18,6 @@ package org.springframework.data.graph.neo4j.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.core.Is;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,8 +37,7 @@ import java.util.Map;
import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.*;
import static org.junit.internal.matchers.IsCollectionContaining.hasItems;
import static org.neo4j.helpers.collection.IteratorUtil.asCollection;
import static org.springframework.data.graph.neo4j.Person.persistedPerson;
@@ -77,7 +75,7 @@ public class GraphRepositoryTest {
@Test
@Transactional
public void testFindIterableOfPersonWithQueryAnnotation() {
final TestTeam testTeam = new TestTeam(graphDatabaseContext);
final TestTeam testTeam = new TestTeam();
testTeam.createSDGTeam();
Iterable<Person> teamMembers = personRepository.findAllTeamMembers(testTeam.sdg);
assertThat(asCollection(teamMembers), hasItems(testTeam.michael,testTeam.david,testTeam.emil));
@@ -85,7 +83,7 @@ public class GraphRepositoryTest {
@Test
@Transactional
public void testFindPersonWithQueryAnnotation() {
final TestTeam testTeam = new TestTeam(graphDatabaseContext);
final TestTeam testTeam = new TestTeam();
testTeam.createSDGTeam();
Person boss = personRepository.findBoss(testTeam.michael);
assertThat(boss, is(testTeam.emil));
@@ -93,7 +91,7 @@ public class GraphRepositoryTest {
@Test
@Transactional
public void testFindIterableMapsWithQueryAnnotation() {
final TestTeam testTeam = new TestTeam(graphDatabaseContext);
final TestTeam testTeam = new TestTeam();
testTeam.createSDGTeam();
Iterable<Map<String,Object>> teamMembers = personRepository.findAllTeamMemberData(testTeam.sdg);
assertThat(asCollection(teamMembers), hasItems(testTeam.simpleRowFor(testTeam.michael,"member"),testTeam.simpleRowFor(testTeam.david,"member"),testTeam.simpleRowFor(testTeam.emil,"member")));
@@ -151,6 +149,20 @@ public class GraphRepositoryTest {
assertEquals(p, pById);
}
@Test
@Transactional
public void testExists() {
Person p = persistedPerson("Michael", 35);
boolean found = personRepository.exists(p.getNodeId());
assertTrue("Found persisted entity", found);
}
@Test
@Transactional
public void testDoesntExist() {
boolean found = personRepository.exists(Long.MAX_VALUE-1);
assertFalse("Non existend id isn't foundpo ", found);
}
@Test
@Transactional
public void testFinderFindByIdNonexistent() {
@@ -162,9 +174,9 @@ public class GraphRepositoryTest {
@Test
@Transactional
public void testFinderCount() {
assertEquals((Long)0L, personRepository.count());
assertEquals(0L, personRepository.count());
Person p = persistedPerson("Michael", 35);
assertEquals((Long)1L, personRepository.count());
assertEquals(1L, personRepository.count());
}
@Test

View File

@@ -29,7 +29,6 @@ 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;
import static org.junit.internal.matchers.IsCollectionContaining.hasItems;
@@ -49,7 +48,7 @@ public class NodeEntityQueryTest {
@Before
public void setUp() throws Exception {
testTeam = new TestTeam(graphDatabaseContext);
testTeam = new TestTeam();
testTeam.createSDGTeam();
michael = testTeam.michael;
}

View File

@@ -200,16 +200,16 @@ public class SubReferenceNodeTypeRepresentationStrategyTest {
log.warn("Created volvo");
new Toyota().persist();
log.warn("Created volvo");
assertEquals("Wrong count for Volvo.", (Long)1L, graphRepositoryFactory.createGraphRepository(Volvo.class).count());
assertEquals("Wrong count for Toyota.", (Long)1L, graphRepositoryFactory.createGraphRepository(Toyota.class).count());
assertEquals("Wrong count for Car.", (Long)2L, graphRepositoryFactory.createGraphRepository(Car.class).count());
assertEquals("Wrong count for Volvo.", 1L, graphRepositoryFactory.createGraphRepository(Volvo.class).count());
assertEquals("Wrong count for Toyota.", 1L, graphRepositoryFactory.createGraphRepository(Toyota.class).count());
assertEquals("Wrong count for Car.", 2L, graphRepositoryFactory.createGraphRepository(Car.class).count());
}
@Test
@Transactional
public void testCountClasses() {
persistedPerson("Michael", 36);
persistedPerson("David", 25);
assertEquals("Wrong Person instance count.", (Long)2L, graphRepositoryFactory.createGraphRepository(Person.class).count());
assertEquals("Wrong Person instance count.", 2L, graphRepositoryFactory.createGraphRepository(Person.class).count());
}

View File

@@ -28,14 +28,12 @@ import java.util.Map;
* @since 13.06.11
*/
public class TestTeam {
private final GraphDatabaseContext graphDatabaseContext;
public Person michael;
public Person emil;
public Person david;
public Group sdg;
public TestTeam(GraphDatabaseContext graphDatabaseContext) {
this.graphDatabaseContext = graphDatabaseContext;
public TestTeam() {
}
public void createSDGTeam() {

View File

@@ -0,0 +1,150 @@
/**
* 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.graph.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.GraphDatabaseService;
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.graph.core.GraphDatabase;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.Person;
import org.springframework.data.graph.neo4j.Personality;
import org.springframework.data.graph.neo4j.support.DelegatingGraphDatabase;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.data.graph.neo4j.support.TestTeam;
import org.springframework.data.graph.neo4j.support.node.Neo4jHelper;
import org.springframework.data.graph.neo4j.template.NeoApiTest;
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/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml"})
@Transactional
public class QueryEngineTest {
@Autowired
protected ConversionService conversionService;
@Autowired
private GraphDatabaseContext graphDatabaseContext;
private QueryEngine queryEngine;
private TestTeam testTeam;
private Person michael;
private GraphDatabase graphDatabase;
@Before
public void setUp() throws Exception {
graphDatabase = createGraphDatabase();
testTeam = new TestTeam();
testTeam.createSDGTeam();
queryEngine = 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(queryEngine.query(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(queryEngine.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(queryEngine.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(queryEngine.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 = queryEngine.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 = queryEngine.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 = queryEngine.queryForObject(queryString, Personality.class);
assertEquals(michael.getPersonality(),result);
}
}

View File

@@ -54,7 +54,7 @@ public class QueryExecutorTest {
@Before
public void setUp() throws Exception {
testTeam = new TestTeam(graphDatabaseContext);
testTeam = new TestTeam();
testTeam.createSDGTeam();
queryExecutor = new QueryExecutor(graphDatabaseContext);
michael = testTeam.michael;

View File

@@ -21,7 +21,9 @@ import org.junit.Before;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.test.ImpermanentGraphDatabase;
import org.neo4j.kernel.impl.transaction.SpringTransactionManager;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.graph.core.GraphDatabase;
import org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean;
import org.springframework.data.graph.neo4j.support.DelegatingGraphDatabase;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
@@ -31,16 +33,22 @@ public abstract class NeoApiTest {
protected Neo4jTemplate template;
protected PlatformTransactionManager transactionManager;
private GraphDatabaseService graphDatabaseService;
protected ConversionService conversionService;
@Before
public void setUp() throws Exception
{
conversionService = createConversionService();
graph = createGraphDatabase();
transactionManager = createTransactionManager();
template = new Neo4jTemplate(graph, transactionManager);
}
private ConversionService createConversionService() throws Exception {
return new Neo4jConversionServiceFactoryBean().getObject();
}
protected PlatformTransactionManager createTransactionManager()
{
return new JtaTransactionManager(new SpringTransactionManager(graphDatabaseService));

View File

@@ -82,11 +82,10 @@
<!--constructor-arg index="0" value="${neo4j.databaseDirectory}" /-->
</bean>
<bean id="conversionService" class="org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
<bean id="graphDatabaseContext" class="org.springframework.data.graph.neo4j.support.GraphDatabaseContext">
<property name="graphDatabaseService" ref="graphDatabaseService"/>
<property name="conversionService">
<bean class="org.springframework.data.graph.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
</property>
<property name="conversionService" ref="conversionService"/>
<property name="nodeTypeRepresentationStrategy" ref="nodeTypeRepresentationStrategy"/>
<property name="relationshipTypeRepresentationStrategy" ref="relationshipTypeRepresentationStrategy"/>
<property name="validator">