added cypher-queries
This commit is contained in:
@@ -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>
|
||||
* @GraphTraversal(traversalBuilder=FriendTraversalBuilder.class, elementClass=Person.class)
|
||||
* Iterable<Person> 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 {};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user