added cypher-queries

This commit is contained in:
Michael Hunger
2011-06-13 01:07:27 +02:00
parent d179725f82
commit e5e97a1ff6
4 changed files with 318 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
/**
* 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.annotation;
import org.springframework.data.graph.core.FieldTraversalDescriptionBuilder;
import org.springframework.data.graph.core.NodeBacked;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Field that provides access to an iterator which is created by applying the traversal that is built by the supplied
* traversal builder to the current node. The result elements are automatically converted to appropriate element
* entity class instances.
* <pre>
* &#64;GraphTraversal(traversalBuilder=FriendTraversalBuilder.class, elementClass=Person.class)
* Iterable&lt;Person&gt; friends;
* </pre>
* @author Michael Hunger
* @since 15.09.2010
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface GraphQuery {
/**
* @return Query to be executed %d will be replaced by the node-id of the current entity other placeholders by the given params
*/
String value() default "";
/**
* @return target type to convert the single result column (if any) to.
*/
Class<?> elementClass() default Object.class;
/**
* @return parameters that are replaced in the to the @see query-string
*/
String[] params() default {};
}

View File

@@ -0,0 +1,100 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.graph.annotation.GraphQuery;
import org.springframework.data.graph.annotation.GraphTraversal;
import org.springframework.data.graph.core.FieldTraversalDescriptionBuilder;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.support.node.Neo4jNodeBacking;
import org.springframework.data.graph.neo4j.support.query.QueryExecutor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import static org.springframework.data.graph.neo4j.support.DoReturn.doReturn;
public class QueryFieldAccessorFactory implements FieldAccessorFactory<NodeBacked> {
@Override
public boolean accept(final Field f) {
final GraphQuery graphQuery = f.getAnnotation(GraphQuery.class);
return graphQuery != null
&& !graphQuery.value().isEmpty();
}
@Override
public FieldAccessor<NodeBacked> forField(final Field field) {
return new QueryFieldAccessor(field);
}
/**
* @author Michael Hunger
* @since 12.09.2010
*/
public static class QueryFieldAccessor implements FieldAccessor<NodeBacked> {
protected final Field field;
private final String query;
private Class<?> target;
protected String[] params;
private boolean iterableResult;
public QueryFieldAccessor(final Field field) {
this.field = field;
final GraphQuery graphQuery = field.getAnnotation(GraphQuery.class);
this.target = graphQuery.elementClass();
this.params = graphQuery.params();
this.query = graphQuery.value();
this.iterableResult = Iterable.class.isAssignableFrom(field.getType());
}
@Override
public boolean isWriteable(NodeBacked nodeBacked) {
return false;
}
@Override
public Object setValue(final NodeBacked nodeBacked, final Object newVal) {
throw new InvalidDataAccessApiUsageException("Cannot set readonly query field " + field);
}
@Override
public Object getValue(final NodeBacked nodeBacked) {
final String queryString = String.format(this.query, createPlaceholderParams(nodeBacked));
return doReturn(executeQuery(nodeBacked, queryString));
}
private Object executeQuery(NodeBacked nodeBacked, String queryString) {
if (iterableResult) {
if (target.equals(Object.class)) return nodeBacked.findAllByQuery(queryString);
nodeBacked.findAllByQuery(queryString, this.target);
}
return nodeBacked.findByQuery(query,this.target);
}
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;
}
}
}

View File

@@ -37,8 +37,10 @@ import javax.persistence.Transient;
import javax.persistence.Entity;
import org.springframework.data.graph.neo4j.support.path.EntityPathPathIterableWrapper;
import org.springframework.data.graph.neo4j.support.query.QueryExecutor;
import java.lang.reflect.Field;
import java.util.Map;
import static org.springframework.data.graph.neo4j.support.DoReturn.unwrap;
@@ -173,6 +175,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 Iterable<Map<String,Object>> NodeBacked.findAllByQuery(final String query) {
final QueryExecutor executor = new QueryExecutor(Neo4jNodeBacking.aspectOf().graphDatabaseContext);
return executor.query(query);
}
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 <S extends NodeBacked, E extends NodeBacked> Iterable<EntityPath<S,E>> NodeBacked.findAllPathsByTraversal(TraversalDescription traversalDescription) {
if (!hasPersistentState()) throw new IllegalStateException("No node attached to " + this);

View File

@@ -0,0 +1,147 @@
/**
* 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.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.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author mh
* @since 10.06.11
* todo limits
*/
public class QueryExecutor {
private final TypeRepresentationStrategy nodeTypeRepresentationStrategy;
private final ExecutionEngine executionEngine;
private final TypeRepresentationStrategy relationshipTypeRepresentationStrategy;
private final ConversionService conversionService;
public QueryExecutor(GraphDatabaseContext ctx) {
this.nodeTypeRepresentationStrategy = ctx.getNodeTypeRepresentationStrategy();
relationshipTypeRepresentationStrategy = ctx.getRelationshipTypeRepresentationStrategy();
conversionService = ctx.getConversionService();
this.executionEngine = new ExecutionEngine(ctx.getGraphDatabaseService());
}
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);
}
}
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);
}
}
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) {
for (Map.Entry<String, Object> entry : row.entrySet()) {
Object value = convertValue(entry.getValue());
if (value != entry.getValue()) {
entry.setValue(value);
}
}
return row;
}
};
}
private Object convertValue(Object value) {
if (value instanceof Node) {
return nodeTypeRepresentationStrategy.createEntity((Node) value);
}
if (value instanceof Relationship) {
return relationshipTypeRepresentationStrategy.createEntity((Relationship) value);
}
return value;
}
private <T> T convertValue(Object value,Class<T> type) {
if (value instanceof Node) {
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);
}
}
}