DATAGRAPH-113 - Implemented derived repository queries for Cypher.
Added query method derivation into Cypher queries according to the wiki. Implemented query execution of derived methods.
This commit is contained in:
@@ -52,7 +52,7 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|
||||
super(field, propertyDescriptor, owner, simpleTypeHolder);
|
||||
this.annotations = extractAnnotations(field);
|
||||
this.relationshipInfo = extractRelationshipInfo(field);
|
||||
this.indexInfo = extractIndexInfo(field);
|
||||
this.indexInfo = extractIndexInfo();
|
||||
this.isIdProperty = annotations.containsKey(GraphId.class);
|
||||
}
|
||||
|
||||
@@ -64,9 +64,9 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|
||||
return result;
|
||||
}
|
||||
|
||||
private IndexInfo extractIndexInfo(Field field) {
|
||||
private IndexInfo extractIndexInfo() {
|
||||
final Indexed annotation = getAnnotation(Indexed.class);
|
||||
return annotation!=null ? new IndexInfo(annotation) : null;
|
||||
return annotation!=null ? new IndexInfo(annotation,this) : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -196,13 +196,21 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty<Neo4jPersis
|
||||
private final String fieldName;
|
||||
private final Indexed.Level level;
|
||||
|
||||
public IndexInfo(Indexed annotation) {
|
||||
this.indexName = annotation.indexName();
|
||||
public IndexInfo(Indexed annotation, Neo4jPersistentPropertyImpl property) {
|
||||
this.indexName = determineIndexName(annotation,property);
|
||||
this.fulltext = annotation.fulltext();
|
||||
fieldName = annotation.fieldName();
|
||||
level = annotation.level();
|
||||
}
|
||||
|
||||
|
||||
private String determineIndexName(Indexed annotation, Neo4jPersistentPropertyImpl property) {
|
||||
final String providedIndexName = annotation.indexName().isEmpty() ? null : annotation.indexName();
|
||||
final Class<?> declaringClass = property.getField().getDeclaringClass();
|
||||
final Class<?> instanceType = property.getOwner().getType();
|
||||
return Indexed.Name.get(annotation.level(), declaringClass, providedIndexName, instanceType);
|
||||
}
|
||||
|
||||
public String getIndexName() {
|
||||
return indexName;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
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 org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.PropertyContainer;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
@@ -23,12 +30,11 @@ 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.NodeEntity;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
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.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.repository.query.DerivedCypherRepositoryQuery;
|
||||
import org.springframework.data.neo4j.support.GenericTypeExtractor;
|
||||
import org.springframework.data.neo4j.support.GraphDatabaseContext;
|
||||
import org.springframework.data.neo4j.support.conversion.EntityResultConverter;
|
||||
@@ -38,32 +44,36 @@ import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.*;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
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
|
||||
* @since 28.03.11
|
||||
*/
|
||||
public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
|
||||
private final GraphDatabaseContext graphDatabaseContext;
|
||||
private final MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext;
|
||||
|
||||
/**
|
||||
* Creates a new {@link GraphRepositoryFactory} from the given {@link GraphDatabaseContext} and
|
||||
* {@link MappingContext}.
|
||||
*
|
||||
* @param graphDatabaseContext must not be {@literal null}.
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
public GraphRepositoryFactory(GraphDatabaseContext graphDatabaseContext, MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext) {
|
||||
|
||||
public GraphRepositoryFactory(GraphDatabaseContext graphDatabaseContext) {
|
||||
Assert.notNull(graphDatabaseContext);
|
||||
Assert.notNull(mappingContext);
|
||||
|
||||
this.graphDatabaseContext = graphDatabaseContext;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +92,6 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
protected Object getTargetRepository(RepositoryMetadata metadata, GraphDatabaseContext graphDatabaseContext) {
|
||||
|
||||
Class<?> repositoryInterface = metadata.getRepositoryInterface();
|
||||
Class<?> type = metadata.getDomainClass();
|
||||
GraphEntityInformation entityInformation = (GraphEntityInformation)getEntityInformation(type);
|
||||
// todo entityInformation.isGraphBacked();
|
||||
@@ -96,14 +105,20 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
@Override
|
||||
protected Class<?> getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) {
|
||||
Class<?> domainClass = repositoryMetadata.getDomainClass();
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
final GraphEntityInformation entityInformation = (GraphEntityInformation) getEntityInformation(domainClass);
|
||||
if (entityInformation.isNodeEntity()) return NodeGraphRepository.class;
|
||||
if (entityInformation.isRelationshipEntity()) return RelationshipGraphRepository.class;
|
||||
if (entityInformation.isNodeEntity()) {
|
||||
return NodeGraphRepository.class;
|
||||
}
|
||||
if (entityInformation.isRelationshipEntity()) {
|
||||
return RelationshipGraphRepository.class;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid Domain Class "+ domainClass+" neither Node- nor RelationshipEntity");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public <T, ID extends Serializable> EntityInformation<T, ID> getEntityInformation(Class<T> type) {
|
||||
return new GraphMetamodelEntityInformation(type,graphDatabaseContext);
|
||||
}
|
||||
@@ -116,12 +131,17 @@ 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.hasAnnotation() && !namedQueries.hasQuery(queryMethod.getNamedQueryName())) {
|
||||
return new DerivedCypherRepositoryQuery(mappingContext, queryMethod, graphDatabaseContext);
|
||||
}
|
||||
|
||||
return queryMethod.createQuery(repositoryMetadata, GraphRepositoryFactory.this.graphDatabaseContext);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static class GraphQueryMethod extends QueryMethod {
|
||||
public static class GraphQueryMethod extends QueryMethod {
|
||||
|
||||
private final Method method;
|
||||
private final Query queryAnnotation;
|
||||
@@ -132,7 +152,6 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
this.method = method;
|
||||
queryAnnotation = method.getAnnotation(Query.class);
|
||||
this.query = queryAnnotation != null ? queryAnnotation.value() : getNamedQuery(namedQueries);
|
||||
if (this.query==null) throw new IllegalArgumentException("Could not extract a query from "+method);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
@@ -179,19 +198,28 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private Pageable getPageable(Object[] args) {
|
||||
Parameters parameters = getParameters();
|
||||
if (parameters.hasPageableParameter()) return (Pageable) args[parameters.getPageableIndex()];
|
||||
if (parameters.hasPageableParameter()) {
|
||||
return (Pageable) args[parameters.getPageableIndex()];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String addPaging(String baseQuery, Pageable pageable) {
|
||||
if (pageable==null) return baseQuery;
|
||||
if (pageable==null) {
|
||||
return baseQuery;
|
||||
}
|
||||
return baseQuery + " skip "+pageable.getOffset() + " limit " + pageable.getPageSize();
|
||||
}
|
||||
|
||||
private String addSorting(String baseQuery, Sort sort) {
|
||||
if (sort==null) return baseQuery; // || sort.isEmpty()
|
||||
if (sort==null)
|
||||
{
|
||||
return baseQuery; // || sort.isEmpty()
|
||||
}
|
||||
final String sortOrder = getSortOrder(sort);
|
||||
if (sortOrder.isEmpty()) return baseQuery;
|
||||
if (sortOrder.isEmpty()) {
|
||||
return baseQuery;
|
||||
}
|
||||
return baseQuery + " order by " + sortOrder;
|
||||
}
|
||||
|
||||
@@ -217,7 +245,9 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private Class<?> getCompoundType() {
|
||||
final Class<?> elementClass = getElementClass();
|
||||
if (elementClass!=null) return elementClass;
|
||||
if (elementClass!=null) {
|
||||
return elementClass;
|
||||
}
|
||||
return GenericTypeExtractor.resolveReturnedType(method);
|
||||
}
|
||||
|
||||
@@ -241,17 +271,19 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
}
|
||||
|
||||
private RepositoryQuery createQuery(RepositoryMetadata repositoryMetadata, final GraphDatabaseContext context) {
|
||||
if (!isValid()) return null;
|
||||
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");
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,6 +298,7 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
queryExecutor = new CypherQueryExecutor(graphDatabaseContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
GraphQueryMethod queryMethod = getQueryMethod();
|
||||
final Class<?> compoundType = queryMethod.getCompoundType();
|
||||
@@ -273,7 +306,9 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
return queryPaged(queryString,params,pageable);
|
||||
}
|
||||
if (queryMethod.isIterableResult()) {
|
||||
if (compoundType.isAssignableFrom(Map.class)) return queryExecutor.queryForList(queryString,params);
|
||||
if (compoundType.isAssignableFrom(Map.class)) {
|
||||
return queryExecutor.queryForList(queryString,params);
|
||||
}
|
||||
return queryExecutor.query(queryString, queryMethod.getCompoundType(),params);
|
||||
}
|
||||
return queryExecutor.queryForObject(queryString, queryMethod.getReturnType(),params);
|
||||
@@ -290,9 +325,10 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
public GremlinGraphRepositoryQuery(GraphQueryMethod queryMethod, RepositoryMetadata metadata, final GraphDatabaseContext graphDatabaseContext) {
|
||||
super(queryMethod, metadata, graphDatabaseContext);
|
||||
queryExecutor = new GremlinQueryEngine(graphDatabaseContext.getGraphDatabaseService(), new EntityResultConverter(graphDatabaseContext));
|
||||
queryExecutor = new GremlinQueryEngine(graphDatabaseContext.getGraphDatabaseService(), new EntityResultConverter<Object, Object>(graphDatabaseContext));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object dispatchQuery(String queryString, Map<String, Object> params, Pageable pageable) {
|
||||
GraphQueryMethod queryMethod = getQueryMethod();
|
||||
if (queryMethod.isPageQuery()) {
|
||||
@@ -334,10 +370,13 @@ public class GraphRepositoryFactory extends RepositoryFactorySupport {
|
||||
return queryMethod;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
protected Object createPage(Iterable<?> result, Pageable pageable) {
|
||||
final List resultList = IteratorUtil.addToCollection(result, new ArrayList());
|
||||
if (pageable==null) return new PageImpl(resultList);
|
||||
if (pageable==null) {
|
||||
return new PageImpl(resultList);
|
||||
}
|
||||
final int currentTotal = pageable.getOffset() + pageable.getPageSize();
|
||||
return new PageImpl(resultList, pageable, currentTotal);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
import org.neo4j.graphdb.PropertyContainer;
|
||||
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.GraphDatabaseContext;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
|
||||
@@ -28,29 +30,44 @@ import org.springframework.util.Assert;
|
||||
* @author mh
|
||||
* @since 28.03.11
|
||||
*/
|
||||
public class GraphRepositoryFactoryBean<S extends PropertyContainer, R extends CRUDRepository<T>, T>
|
||||
extends TransactionalRepositoryFactoryBeanSupport<R, T, Long> {
|
||||
public class GraphRepositoryFactoryBean<S extends PropertyContainer, R extends CRUDRepository<T>, T> extends
|
||||
TransactionalRepositoryFactoryBeanSupport<R, T, Long> {
|
||||
|
||||
private GraphDatabaseContext graphDatabaseContext;
|
||||
private MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext;
|
||||
|
||||
public void setGraphDatabaseContext(GraphDatabaseContext graphDatabaseContext) {
|
||||
this.graphDatabaseContext = graphDatabaseContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mappingContext the mappingContext to set
|
||||
*/
|
||||
public void setMappingContext(
|
||||
MappingContext<Neo4jPersistentEntity<?>, Neo4jPersistentProperty> mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RepositoryFactorySupport doCreateRepositoryFactory() {
|
||||
return createRepositoryFactory(graphDatabaseContext);
|
||||
}
|
||||
|
||||
|
||||
protected RepositoryFactorySupport createRepositoryFactory(GraphDatabaseContext graphDatabaseContext) {
|
||||
|
||||
return new GraphRepositoryFactory(graphDatabaseContext);
|
||||
return new GraphRepositoryFactory(graphDatabaseContext, mappingContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.notNull(graphDatabaseContext, "GraphDatabaseContext must not be null!");
|
||||
|
||||
if (mappingContext == null) {
|
||||
Neo4jMappingContext context = new Neo4jMappingContext();
|
||||
context.afterPropertiesSet();
|
||||
this.mappingContext = context;
|
||||
}
|
||||
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
@@ -1,66 +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.repository.query;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object to create {@link CypherQuery} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class CypherQuery {
|
||||
|
||||
private final String TEMPLATE = "start %s match % where %";
|
||||
|
||||
private final Neo4jMappingContext context;
|
||||
private final TypeInformation<?> rootType;
|
||||
|
||||
private final Set<MatchClause> matchClauses;
|
||||
|
||||
public CypherQuery(TypeInformation<?> rootType, Neo4jMappingContext context) {
|
||||
|
||||
Assert.notNull(rootType);
|
||||
Assert.notNull(context);
|
||||
|
||||
this.rootType = rootType;
|
||||
this.context = context;
|
||||
this.matchClauses = new HashSet<MatchClause>();
|
||||
}
|
||||
|
||||
public CypherQuery addRestriction(Part part) {
|
||||
|
||||
matchClauses.add(new MatchClause(context, part.getProperty()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(TEMPLATE, "TODO", StringUtils.collectionToCommaDelimitedString(matchClauses), "TODO");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to create Cypher queries.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class CypherQueryBuilder implements CypherQueryDefinition {
|
||||
|
||||
private final MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context;
|
||||
|
||||
private final VariableContext variableContext = new VariableContext();
|
||||
private final List<MatchClause> matchClauses = new ArrayList<MatchClause>();
|
||||
private final List<StartClause> startClauses = new ArrayList<StartClause>();
|
||||
private final List<WhereClause> whereClauses = new ArrayList<WhereClause>();
|
||||
|
||||
private int index = 0;
|
||||
private final Neo4jPersistentEntity<?> entity;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CypherQueryBuilder}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public CypherQueryBuilder(MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context, Class<?> type) {
|
||||
Assert.notNull(context);
|
||||
Assert.notNull(type);
|
||||
|
||||
this.context = context;
|
||||
this.entity = context.getPersistentEntity(type);
|
||||
}
|
||||
|
||||
private String defaultStartClause() {
|
||||
return String.format(QueryTemplates.DEFAULT_START_CLAUSE, this.variableContext.getVariableFor(entity), entity
|
||||
.getType().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given {@link Part} to the restrictions for the query.
|
||||
*
|
||||
* @param part
|
||||
* @return
|
||||
*/
|
||||
public CypherQueryBuilder addRestriction(Part part) {
|
||||
|
||||
PersistentPropertyPath<Neo4jPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
String variable = variableContext.getVariableFor(path);
|
||||
|
||||
Neo4jPersistentProperty leafProperty = path.getLeafProperty();
|
||||
if (!leafProperty.isRelationship()) {
|
||||
if (leafProperty.isIndexed()) {
|
||||
startClauses.add(new StartClause(path, variable, index++));
|
||||
} else {
|
||||
whereClauses.add(new WhereClause(path, variable, part.getType(), index++));
|
||||
}
|
||||
}
|
||||
|
||||
MatchClause matchClause = new MatchClause(path);
|
||||
|
||||
if (matchClause.hasRelationship()) {
|
||||
matchClauses.add(matchClause);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String startClauses = collectionToDelimitedString(this.startClauses, ", ");
|
||||
String matchClauses = toString(this.matchClauses);
|
||||
String whereClauses = collectionToDelimitedString(this.whereClauses, ", ");
|
||||
|
||||
StringBuilder builder = new StringBuilder("start ");
|
||||
|
||||
if (hasText(startClauses)) {
|
||||
builder.append(startClauses);
|
||||
} else {
|
||||
builder.append(defaultStartClause());
|
||||
}
|
||||
|
||||
if (hasText(matchClauses)) {
|
||||
builder.append(" match ").append(matchClauses);
|
||||
}
|
||||
|
||||
if (hasText(whereClauses)) {
|
||||
builder.append(" where ").append(whereClauses);
|
||||
}
|
||||
|
||||
builder.append(" return ").append(variableContext.getVariableFor(entity));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.neo4j.repository.query.CypherQueryDefinition#toString(org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public String toString(Pageable pageable) {
|
||||
|
||||
StringBuilder builder = new StringBuilder(toString(pageable.getSort()));
|
||||
|
||||
if (pageable != null) {
|
||||
builder.append(String.format(QueryTemplates.SKIP_LIMIT, pageable.getOffset(), pageable.getPageSize()));
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.neo4j.repository.query.CypherQueryDefinition#toString(org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public String toString(Sort sort) {
|
||||
StringBuilder builder = new StringBuilder(toString());
|
||||
builder.append(addSorts(sort));
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String addSorts(Sort sort) {
|
||||
final List<String> sorts = formatSorts(sort);
|
||||
return !sorts.isEmpty() ? String
|
||||
.format(QueryTemplates.ORDER_BY_CLAUSE, collectionToCommaDelimitedString(sorts)) : "";
|
||||
}
|
||||
|
||||
private List<String> formatSorts(Sort sort) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
if (sort == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Sort.Order order : sort) {
|
||||
result.add(String.format(QueryTemplates.SORT_CLAUSE, order.getProperty(), order.getDirection()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private String toString(List<MatchClause> matchClauses) {
|
||||
List<String> result = new ArrayList<String>(matchClauses.size());
|
||||
for (MatchClause matchClause : matchClauses) {
|
||||
result.add(matchClause.toString(variableContext));
|
||||
}
|
||||
return collectionToDelimitedString(result, ", ");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link AbstractQueryCreator} implementation to build {@link CypherQueryDefinition}s.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class CypherQueryCreator extends AbstractQueryCreator<CypherQueryDefinition, CypherQueryBuilder> {
|
||||
|
||||
private final MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context;
|
||||
private final Class<?> domainClass;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CypherQueryCreator} using the given {@link PartTree}, {@link Neo4jMappingContext} and domain
|
||||
* class.
|
||||
*
|
||||
* @param tree must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param domainClass must not be {@literal null}.
|
||||
*/
|
||||
public CypherQueryCreator(PartTree tree, MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context, Class<?> domainClass) {
|
||||
|
||||
super(tree);
|
||||
|
||||
Assert.notNull(context);
|
||||
Assert.notNull(domainClass);
|
||||
|
||||
this.context = context;
|
||||
this.domainClass = domainClass;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#create(org.springframework.data.repository.query.parser.Part, java.util.Iterator)
|
||||
*/
|
||||
@Override
|
||||
protected CypherQueryBuilder create(Part part, Iterator<Object> iterator) {
|
||||
|
||||
CypherQueryBuilder builder = new CypherQueryBuilder(context, domainClass);
|
||||
builder.addRestriction(part);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#and(org.springframework.data.repository.query.parser.Part, java.lang.Object, java.util.Iterator)
|
||||
*/
|
||||
@Override
|
||||
protected CypherQueryBuilder and(Part part, CypherQueryBuilder base, Iterator<Object> iterator) {
|
||||
|
||||
return base.addRestriction(part);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#or(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected CypherQueryBuilder or(CypherQueryBuilder base, CypherQueryBuilder criteria) {
|
||||
throw new UnsupportedOperationException("Or is not supported currently!");
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.parser.AbstractQueryCreator#complete(java.lang.Object, org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
protected CypherQueryDefinition complete(CypherQueryBuilder criteria, Sort sort) {
|
||||
return criteria;
|
||||
}
|
||||
}
|
||||
@@ -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.repository.query;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
/**
|
||||
* Interface to abstract Cypher query creation.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
interface CypherQueryDefinition {
|
||||
|
||||
/**
|
||||
* Returns a Cypher query without adding any sort or pagination.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String toString();
|
||||
|
||||
/**
|
||||
* Returns a Cypher query adding the given {@link Sort}.
|
||||
*
|
||||
* @param sort
|
||||
* @return
|
||||
*/
|
||||
String toString(Sort sort);
|
||||
|
||||
/**
|
||||
* Returns a Cypher query restricting the result to the given {@link Pageable} and applying the {@link Sort}
|
||||
* contained in it.
|
||||
*
|
||||
* @param pageable
|
||||
* @return
|
||||
*/
|
||||
String toString(Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.repository.GraphRepositoryFactory.GraphQueryMethod;
|
||||
import org.springframework.data.neo4j.support.GraphDatabaseContext;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
import org.springframework.data.repository.core.EntityMetadata;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.ParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.parser.PartTree;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link RepositoryQuery} implementation that derives a Cypher query from the {@link GraphQueryMethod}'s method name.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DerivedCypherRepositoryQuery implements RepositoryQuery {
|
||||
|
||||
private final GraphQueryMethod method;
|
||||
private final CypherQueryExecutor executor;
|
||||
private final CypherQueryDefinition query;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DerivedCypherRepositoryQuery} from the given {@link MappingContext},
|
||||
* {@link GraphQueryMethod} and {@link GraphDatabaseContext}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
* @param database must not be {@literal null}.
|
||||
*/
|
||||
public DerivedCypherRepositoryQuery(MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context, GraphQueryMethod method, GraphDatabaseContext database) {
|
||||
|
||||
Assert.notNull(context);
|
||||
Assert.notNull(method);
|
||||
Assert.notNull(database);
|
||||
|
||||
EntityMetadata<?> info = method.getEntityInformation();
|
||||
PartTree tree = new PartTree(method.getName(), info.getJavaType());
|
||||
|
||||
this.query = new CypherQueryCreator(tree, context, info.getJavaType()).createQuery();
|
||||
this.method = method;
|
||||
this.executor = new CypherQueryExecutor(database);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public Object execute(Object[] parameters) {
|
||||
|
||||
ParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), parameters);
|
||||
|
||||
Map<String, Object> paramMap = new HashMap<String, Object>();
|
||||
int counter = 0;
|
||||
|
||||
for (Object parameter : accessor) {
|
||||
paramMap.put(String.format(QueryTemplates.PARAMETER, counter++), parameter);
|
||||
}
|
||||
|
||||
Class<?> type = method.getEntityInformation().getJavaType();
|
||||
String query = getQuery(this.query, accessor);
|
||||
|
||||
if (method.isCollectionQuery()) {
|
||||
return executor.query(query, type, paramMap);
|
||||
} else {
|
||||
return executor.queryForObject(query, type, paramMap);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
|
||||
*/
|
||||
@Override
|
||||
public QueryMethod getQueryMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual Cypher query applying {@link Pageable} or {@link Sort} instances.
|
||||
*
|
||||
* @param query
|
||||
* @param accessor
|
||||
* @return
|
||||
*/
|
||||
private String getQuery(CypherQueryDefinition query, ParameterAccessor accessor) {
|
||||
|
||||
if (accessor.getPageable() != null) {
|
||||
return query.toString(accessor.getPageable());
|
||||
} else if (accessor.getSort() != null) {
|
||||
return query.toString(accessor.getSort());
|
||||
} else {
|
||||
return query.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,94 +13,63 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.mapping.RelationshipInfo;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Value object to build the {@code match} clause of a Cypher query.
|
||||
* Value object to build the {@literal match} clause of a Cypher query.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class MatchClause {
|
||||
|
||||
private final Iterable<Neo4jPersistentProperty> properties;
|
||||
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MatchClause} using the given
|
||||
* {@link org.springframework.data.neo4j.mapping.Neo4jMappingContext} and {@link PropertyPath}.
|
||||
* Creates a new {@link MatchClause} using the given {@link PersistentPropertyPath}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
* @param property must not be {@literal null}.
|
||||
* @param path must not be {@literal null}.
|
||||
*/
|
||||
public MatchClause(Neo4jMappingContext context, PropertyPath property) {
|
||||
|
||||
Assert.notNull(context);
|
||||
Assert.notNull(property);
|
||||
|
||||
this.properties = context.getPersistentPropertyPath(property);
|
||||
public MatchClause(PersistentPropertyPath<Neo4jPersistentProperty> path) {
|
||||
Assert.notNull(path);
|
||||
this.path = relationshipPath(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
private PersistentPropertyPath<Neo4jPersistentProperty> relationshipPath(
|
||||
PersistentPropertyPath<Neo4jPersistentProperty> path) {
|
||||
|
||||
return (path.getLength() == 1 || path.getLeafProperty().isRelationship()) ? path : relationshipPath(path
|
||||
.getParentPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the match clause actually deals with a relationship.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String intermediate = null;
|
||||
|
||||
for (Neo4jPersistentProperty property : properties) {
|
||||
|
||||
if (!property.isRelationship()) {
|
||||
return intermediate;
|
||||
public boolean hasRelationship() {
|
||||
for (Neo4jPersistentProperty property : path) {
|
||||
if (property.isRelationship()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RelationshipInfo info = property.getRelationshipInfo();
|
||||
Class<?> ownerType = property.getOwner().getType();
|
||||
|
||||
intermediate = intermediate == null ? asVariableReference(StringUtils.uncapitalize(ownerType
|
||||
.getSimpleName())) : intermediate;
|
||||
intermediate = String.format(getPattern(info), intermediate, info.getType(),
|
||||
asVariableReference(property.getName()));
|
||||
}
|
||||
|
||||
return intermediate.toString();
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given value as variable reference.
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
private static String asVariableReference(String value) {
|
||||
return String.format("(%s)", value);
|
||||
public String toString(VariableContext variableContext) {
|
||||
return matchPattern(variableContext, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the clause pattern for the given {@link RelationshipInfo}.
|
||||
*
|
||||
* @param info must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static String getPattern(RelationshipInfo info) {
|
||||
|
||||
switch (info.getDirection()) {
|
||||
case OUTGOING:
|
||||
return "%s-[:%s]->%s";
|
||||
case INCOMING:
|
||||
return "%s<-[:%s]-%s";
|
||||
case BOTH:
|
||||
return "%s-[:%s]-%s";
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported direction!");
|
||||
private String matchPattern(VariableContext variableContext, PersistentPropertyPath<Neo4jPersistentProperty> relPath) {
|
||||
if (relPath.getLength() == 1) {
|
||||
final Neo4jPersistentProperty property = relPath.getBaseProperty();
|
||||
return variableContext.getVariableFor(property.getOwner()) + QueryTemplates.getArrow(property.getRelationshipInfo())
|
||||
+ variableContext.getVariableFor(relPath);
|
||||
}
|
||||
final RelationshipInfo info = relPath.getLeafProperty().getRelationshipInfo();
|
||||
return matchPattern(variableContext, relPath.getParentPath()) + QueryTemplates.getArrow(info)
|
||||
+ variableContext.getVariableFor(relPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.springframework.data.neo4j.mapping.RelationshipInfo;
|
||||
|
||||
/**
|
||||
* String templates to build Cypher queries.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
abstract class QueryTemplates {
|
||||
|
||||
static final String PARAMETER = "_%d";
|
||||
|
||||
private static final String PLACEHOLDER = String.format("{%s}", PARAMETER);
|
||||
private static final String DIRECTION_INCOMING = "<-[:%s]-";
|
||||
private static final String DIRECTION_OUTGOING = "-[:%s]->";
|
||||
private static final String DIRECTION_BOTH = "-[:%s]-";
|
||||
|
||||
static final String DEFAULT_START_CLAUSE = "%s=node:__types__(className=\"%s\")";
|
||||
static final String SKIP_LIMIT = " skip %d limit %d";
|
||||
static final String START_CLAUSE = "%s=node:%s(%s=" + PLACEHOLDER + ")";
|
||||
static final String WHERE_CLAUSE = "%s.%s %s " + PLACEHOLDER;
|
||||
static final String SORT_CLAUSE = "%s %s";
|
||||
static final String ORDER_BY_CLAUSE = " order by %s";
|
||||
|
||||
|
||||
static String getArrow(RelationshipInfo info) {
|
||||
return String.format(getTemplate(info.getDirection()), info.getType());
|
||||
}
|
||||
|
||||
private static String getTemplate(Direction direction) {
|
||||
|
||||
switch (direction) {
|
||||
case OUTGOING:
|
||||
return DIRECTION_OUTGOING;
|
||||
case INCOMING:
|
||||
return DIRECTION_INCOMING;
|
||||
case BOTH:
|
||||
return DIRECTION_BOTH;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported direction!");
|
||||
}
|
||||
}
|
||||
|
||||
private QueryTemplates() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,52 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.data.neo4j.repository.query;
|
||||
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Representation of a Cypher {@literal start} clause.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class StartClause {
|
||||
|
||||
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
|
||||
private final String variable;
|
||||
private final int index;
|
||||
|
||||
/**
|
||||
* Creates a new {@link StartClause} from the given {@link Neo4jPersistentProperty}, variable and the given
|
||||
* parameter index.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @param variable must not be {@literal null} or empty.
|
||||
* @param index
|
||||
*/
|
||||
public StartClause(PersistentPropertyPath<Neo4jPersistentProperty> property, String variable, int index) {
|
||||
|
||||
Assert.notNull(property);
|
||||
Assert.hasText(variable);
|
||||
|
||||
this.path = property;
|
||||
this.variable = variable;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
Neo4jPersistentProperty leafProperty = path.getLeafProperty();
|
||||
String indexName = leafProperty.getIndexInfo().getIndexName();
|
||||
String propertyName = leafProperty.getNeo4jPropertyName();
|
||||
|
||||
return String.format(QueryTemplates.START_CLAUSE, variable, indexName, propertyName, index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class VariableContext {
|
||||
|
||||
private Map<PersistentPropertyPath<Neo4jPersistentProperty>, String> variables;
|
||||
|
||||
public VariableContext() {
|
||||
this.variables = new HashMap<PersistentPropertyPath<Neo4jPersistentProperty>, String>();
|
||||
}
|
||||
|
||||
public String getVariableFor(PersistentPropertyPath<Neo4jPersistentProperty> path) {
|
||||
|
||||
if (variables.containsKey(path)) {
|
||||
return variables.get(path);
|
||||
}
|
||||
|
||||
Neo4jPersistentProperty baseProperty = path.getBaseProperty();
|
||||
List<String> parts = new ArrayList<String>();
|
||||
parts.add(getVariableFor(baseProperty.getOwner()));
|
||||
|
||||
final Neo4jPersistentProperty leaf = path.getLeafProperty();
|
||||
for (Neo4jPersistentProperty property : path) {
|
||||
if (leaf.isRelationship() || !leaf.equals(property)) {
|
||||
parts.add(property.getName());
|
||||
}
|
||||
}
|
||||
|
||||
String variable = StringUtils.collectionToDelimitedString(parts, "_");
|
||||
variables.put(path, variable);
|
||||
return variable;
|
||||
}
|
||||
|
||||
public String getVariableFor(Neo4jPersistentEntity<?> entity) {
|
||||
return StringUtils.uncapitalize(entity.getType().getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.repository.query.parser.Part.Type;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Representation of a Cypher {@literal where} clause.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class WhereClause {
|
||||
|
||||
private static final Map<Type, String> SYMBOLS;
|
||||
|
||||
static {
|
||||
|
||||
Map<Type, String> symbols = new HashMap<Type, String>();
|
||||
symbols.put(Type.GREATER_THAN, ">");
|
||||
symbols.put(Type.GREATER_THAN_EQUAL, ">=");
|
||||
symbols.put(Type.LESS_THAN, "<");
|
||||
symbols.put(Type.LESS_THAN_EQUAL, "<=");
|
||||
symbols.put(Type.NEGATING_SIMPLE_PROPERTY, "!=");
|
||||
symbols.put(Type.SIMPLE_PROPERTY, "=");
|
||||
|
||||
SYMBOLS = Collections.unmodifiableMap(symbols);
|
||||
}
|
||||
|
||||
private final PersistentPropertyPath<Neo4jPersistentProperty> path;
|
||||
private final String variable;
|
||||
private final Type type;
|
||||
private final int index;
|
||||
|
||||
/**
|
||||
* Creates a new {@link WhereClause} for the given {@link Neo4jPersistentProperty}, variable, type and parameter
|
||||
* index.
|
||||
*
|
||||
* @param path must not be {@literal null}.
|
||||
* @param variable must not be {@literal null} or empty.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param index
|
||||
*/
|
||||
public WhereClause(PersistentPropertyPath<Neo4jPersistentProperty> path, String variable, Type type, int index) {
|
||||
|
||||
Assert.notNull(path);
|
||||
Assert.hasText(variable);
|
||||
Assert.notNull(type);
|
||||
|
||||
this.path = path;
|
||||
this.variable = variable;
|
||||
this.type = type;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(QueryTemplates.WHERE_CLAUSE, variable, path.getLeafProperty().getNeo4jPropertyName(),
|
||||
SYMBOLS.get(type), index);
|
||||
}
|
||||
}
|
||||
@@ -16,27 +16,33 @@
|
||||
|
||||
package org.springframework.data.neo4j.model;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.DynamicRelationshipType;
|
||||
import org.neo4j.graphdb.traversal.TraversalDescription;
|
||||
import org.neo4j.kernel.Traversal;
|
||||
import org.neo4j.kernel.impl.traversal.TraversalDescriptionImpl;
|
||||
import org.springframework.data.neo4j.annotation.*;
|
||||
import org.springframework.data.neo4j.annotation.GraphId;
|
||||
import org.springframework.data.neo4j.annotation.GraphProperty;
|
||||
import org.springframework.data.neo4j.annotation.GraphTraversal;
|
||||
import org.springframework.data.neo4j.annotation.Indexed;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelatedTo;
|
||||
import org.springframework.data.neo4j.core.FieldTraversalDescriptionBuilder;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@NodeEntity
|
||||
public class Group {
|
||||
|
||||
public final static String OTHER_NAME_INDEX="other_name";
|
||||
public final static String OTHER_NAME_INDEX = "other_name";
|
||||
public static final String SEARCH_GROUPS_INDEX = "search-groups";
|
||||
|
||||
@RelatedTo(direction = Direction.OUTGOING)
|
||||
@Fetch
|
||||
private Collection<Person> persons;
|
||||
private Collection<Person> persons = new HashSet<Person>();
|
||||
|
||||
@RelatedTo(type = "persons", elementClass = Person.class)
|
||||
private Iterable<Person> readOnlyPersons;
|
||||
@@ -64,13 +70,13 @@ public class Group {
|
||||
@Indexed(fieldName = OTHER_NAME_INDEX)
|
||||
private String otherName;
|
||||
|
||||
@Indexed(level=Indexed.Level.GLOBAL)
|
||||
@Indexed(level = Indexed.Level.GLOBAL)
|
||||
private String globalName;
|
||||
|
||||
@Indexed(level=Indexed.Level.CLASS)
|
||||
@Indexed(level = Indexed.Level.CLASS)
|
||||
private String classLevelName;
|
||||
|
||||
@Indexed(level=Indexed.Level.INSTANCE)
|
||||
@Indexed(level = Indexed.Level.INSTANCE)
|
||||
private String indexLevelName;
|
||||
|
||||
@GraphId
|
||||
@@ -127,10 +133,9 @@ public class Group {
|
||||
private static class PeopleTraversalBuilder implements FieldTraversalDescriptionBuilder {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public TraversalDescription build(Object start, Neo4jPersistentProperty property, String...params) {
|
||||
return new TraversalDescriptionImpl()
|
||||
.relationships(DynamicRelationshipType.withName(params[0]))
|
||||
.filter(Traversal.returnAllButStartNode());
|
||||
public TraversalDescription build(Object start, Neo4jPersistentProperty property, String... params) {
|
||||
return new TraversalDescriptionImpl().relationships(DynamicRelationshipType.withName(params[0])).filter(
|
||||
Traversal.returnAllButStartNode());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -170,4 +175,30 @@ public class Group {
|
||||
public void setAdmin(Boolean admin) {
|
||||
this.admin = admin;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null || !getClass().equals(obj.getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Group that = (Group) obj;
|
||||
|
||||
return ObjectUtils.nullSafeEquals(this.id, that.id);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return ObjectUtils.nullSafeHashCode(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.data.neo4j.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -25,8 +27,6 @@ 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;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 29.03.11
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* 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.repository;
|
||||
|
||||
import static java.util.Arrays.*;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.internal.matchers.IsCollectionContaining.*;
|
||||
import static org.neo4j.helpers.collection.IteratorUtil.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.model.Group;
|
||||
import org.springframework.data.neo4j.model.Person;
|
||||
import org.springframework.data.neo4j.support.GraphDatabaseContext;
|
||||
import org.springframework.data.neo4j.support.node.Neo4jHelper;
|
||||
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;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
|
||||
@Transactional
|
||||
public class GraphRepositoryTest {
|
||||
|
||||
protected final Log log = LogFactory.getLog(getClass());
|
||||
|
||||
@Autowired
|
||||
private GraphDatabaseContext graphDatabaseContext;
|
||||
|
||||
@Autowired
|
||||
private PersonRepository personRepository;
|
||||
@Autowired
|
||||
GroupRepository groupRepository;
|
||||
|
||||
private TestTeam testTeam;
|
||||
|
||||
@BeforeTransaction
|
||||
public void cleanDb() {
|
||||
Neo4jHelper.cleanDb(graphDatabaseContext);
|
||||
}
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
testTeam = new TestTeam();
|
||||
testTeam.createSDGTeam(personRepository, groupRepository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindIterableOfPersonWithQueryAnnotation() {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembers(testTeam.sdg);
|
||||
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil));
|
||||
}
|
||||
@Test
|
||||
public void testFindIterableOfPersonWithQueryAnnotationAndGremlin() {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembersGremlin(testTeam.sdg);
|
||||
assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPersonWithQueryAnnotation() {
|
||||
Person boss = personRepository.findBoss(testTeam.michael);
|
||||
assertThat(boss, is(testTeam.emil));
|
||||
}
|
||||
@Test
|
||||
public void testFindIterableMapsWithQueryAnnotation() {
|
||||
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")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPaged() {
|
||||
final PageRequest page = new PageRequest(0, 1, Sort.Direction.ASC, "member.name");
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,page);
|
||||
assertThat(teamMemberPage1, hasItem(testTeam.david));
|
||||
}
|
||||
@Test
|
||||
public void testFindPagedDescending() {
|
||||
final PageRequest page = new PageRequest(0, 2, Sort.Direction.DESC, "member.name");
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,page);
|
||||
assertEquals(asList(testTeam.michael, testTeam.emil), asCollection(teamMemberPage1));
|
||||
assertThat(teamMemberPage1.isFirstPage(), is(true));
|
||||
}
|
||||
@Test
|
||||
public void testFindPagedNull() {
|
||||
Page<Person> teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,null);
|
||||
assertEquals(new HashSet(asList(testTeam.david, testTeam.emil, testTeam.michael)), addToCollection(teamMemberPage1, new HashSet()));
|
||||
assertThat(teamMemberPage1.isFirstPage(), is(true));
|
||||
assertThat(teamMemberPage1.isLastPage(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindSortedDescending() {
|
||||
final Sort sort = new Sort(Sort.Direction.DESC, "member.name");
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, sort);
|
||||
assertEquals(asList(testTeam.michael, testTeam.emil, testTeam.david), asCollection(teamMembers));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindSortedNull() {
|
||||
Iterable<Person> teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, null);
|
||||
assertThat(teamMembers, hasItems(testTeam.michael, testTeam.emil, testTeam.david));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindByNamedQuery() {
|
||||
Group team = personRepository.findTeam(testTeam.michael);
|
||||
assertThat(team, is(testTeam.sdg));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByName() {
|
||||
|
||||
Iterable<Person> findByName = personRepository.findByName(testTeam.michael.getName());
|
||||
assertThat(findByName, hasItem(testTeam.michael));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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.repository;
|
||||
|
||||
import org.springframework.data.neo4j.model.Group;
|
||||
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 29.03.11
|
||||
*/
|
||||
public interface GroupRepository extends GraphRepository<Group>, NamedIndexRepository<Group> {
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.repository;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
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.model.Group;
|
||||
import org.springframework.data.neo4j.model.Person;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* Sample repository interface to manage {@link Person}s.
|
||||
*
|
||||
* @author Michael Hunger
|
||||
* @author Oliver Gierke
|
||||
* @since 29.03.11
|
||||
*/
|
||||
public interface PersonRepository extends GraphRepository<Person>, NamedIndexRepository<Person> {
|
||||
|
||||
@Query("start team=node({p_team}) match (team)-[:persons]->(member) return member")
|
||||
Iterable<Person> findAllTeamMembers(@Param("p_team") Group team);
|
||||
|
||||
@Query(value = "g.v(team).out('persons')", type = QueryType.Gremlin)
|
||||
Iterable<Person> findAllTeamMembersGremlin(@Param("team") Group team);
|
||||
|
||||
@Query("start team=node({p_team}) match (team)-[:persons]->(member) return member.name,member.age")
|
||||
Iterable<Map<String,Object>> findAllTeamMemberData(@Param("p_team") Group team);
|
||||
|
||||
@Query("start person=node({p_person}) match (boss)-[:boss]->(person) return boss")
|
||||
Person findBoss(@Param("p_person") Person person);
|
||||
|
||||
Group findTeam(@Param("p_person") Person person);
|
||||
|
||||
@Query("start team=node({p_team}) match (team)-[:persons]->(member) return member")
|
||||
Page<Person> findAllTeamMembersPaged(@Param("p_team") Group team, Pageable page);
|
||||
|
||||
@Query("start team=node({p_team}) match (team)-[:persons]->(member) return member")
|
||||
Iterable<Person> findAllTeamMembersSorted(@Param("p_team") Group team, Sort sort);
|
||||
|
||||
// Derived queries
|
||||
Iterable<Person> findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.repository;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.neo4j.helpers.collection.MapUtil;
|
||||
import org.springframework.data.neo4j.model.Group;
|
||||
import org.springframework.data.neo4j.model.Person;
|
||||
import org.springframework.data.neo4j.model.Personality;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 13.06.11
|
||||
*/
|
||||
public class TestTeam {
|
||||
public Person michael;
|
||||
public Person emil;
|
||||
public Person david;
|
||||
public Group sdg;
|
||||
|
||||
public TestTeam() {
|
||||
}
|
||||
|
||||
public void createSDGTeam(PersonRepository repo, GroupRepository groupRepo) {
|
||||
emil = new Person("Emil", 30);
|
||||
|
||||
michael = new Person("Michael", 36);
|
||||
michael.setBoss(emil);
|
||||
michael.setPersonality(Personality.EXTROVERT);
|
||||
|
||||
david = new Person("David", 25);
|
||||
david.setBoss(emil);
|
||||
|
||||
sdg = new Group();
|
||||
sdg.setName("SDG");
|
||||
sdg.addPerson(michael);
|
||||
sdg.addPerson(emil);
|
||||
sdg.addPerson(david);
|
||||
|
||||
repo.save(Arrays.asList(emil, david, michael));
|
||||
groupRepo.save(sdg);
|
||||
|
||||
}
|
||||
|
||||
public Map<String, Object> simpleRowFor(final Person person, String prefix) {
|
||||
return MapUtil.map(prefix+".name", person.getName(), prefix+".age", person.getAge());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CypherQueryBuilder}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class CypherQueryBuilderUnitTests {
|
||||
|
||||
CypherQueryBuilder query;
|
||||
private final String className = Person.class.getName();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Neo4jMappingContext context = new Neo4jMappingContext();
|
||||
query = new CypherQueryBuilder(context, Person.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsQueryForSimplePropertyReference() {
|
||||
|
||||
Part part = new Part("name", Person.class);
|
||||
query.addRestriction(part);
|
||||
|
||||
assertThat(query.toString(), is("start person=node:Person(name={_0}) return person"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsQueryForPropertyOnRelationShipReference() {
|
||||
|
||||
Part part = new Part("group.name", Person.class);
|
||||
query.addRestriction(part);
|
||||
|
||||
assertThat(query.toString(), is("start person_group=node:Group(name={_0}) match person<-[:members]-person_group return person"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsQueryForMultipleStartClauses() {
|
||||
|
||||
query.addRestriction(new Part("name", Person.class));
|
||||
query.addRestriction(new Part("group.name", Person.class));
|
||||
|
||||
assertThat(query.toString(),
|
||||
is("start person=node:Person(name={_0}), person_group=node:Group(name={_1}) match person<-[:members]-person_group return person"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsSimpleWhereClauseCorrectly() {
|
||||
|
||||
query.addRestriction(new Part("age", Person.class));
|
||||
|
||||
final String className = Person.class.getName();
|
||||
assertThat(query.toString(), is("start person=node:__types__(className=\"" + className + "\") where person.age = {_0} return person"));
|
||||
}
|
||||
@Test
|
||||
public void createsSimpleTraversalClauseCorrectly() {
|
||||
query.addRestriction(new Part("group", Person.class));
|
||||
|
||||
assertThat(query.toString(), is("start person=node:__types__(className=\"" + className + "\") match person<-[:members]-person_group return person"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void buildsComplexQueryCorrectly() {
|
||||
|
||||
query.addRestriction(new Part("name", Person.class));
|
||||
query.addRestriction(new Part("groupName", Person.class));
|
||||
query.addRestriction(new Part("ageGreaterThan", Person.class));
|
||||
query.addRestriction(new Part("groupMembersAge", Person.class));
|
||||
|
||||
System.out.println(query.toString());
|
||||
assertThat(query.toString(), is(
|
||||
"start person=node:Person(name={_0}), person_group=node:Group(name={_1}) " +
|
||||
"match person<-[:members]-person_group, person<-[:members]-person_group-[:members]->person_group_members " +
|
||||
"where person.age > {_2}, person_group_members.age = {_3} " +
|
||||
"return person"
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildsQueryWithSort() {
|
||||
query.addRestriction(new Part("name",Person.class));
|
||||
assertThat(query.toString(new Sort("person.name")), is("start person=node:Person(name={_0}) return person order by person.name ASC"));
|
||||
}
|
||||
@Test
|
||||
public void buildsQueryWithTwoSorts() {
|
||||
query.addRestriction(new Part("name",Person.class));
|
||||
Sort sort = new Sort(new Sort.Order("person.name"),new Sort.Order(Sort.Direction.DESC, "person.age"));
|
||||
assertThat(query.toString(sort), is("start person=node:Person(name={_0}) return person order by person.name ASC,person.age DESC"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildsQueryWithPage() {
|
||||
query.addRestriction(new Part("name",Person.class));
|
||||
Pageable pageable = new PageRequest(3,10,new Sort("person.name"));
|
||||
assertThat(query.toString(pageable), is("start person=node:Person(name={_0}) return person order by person.name ASC skip 30 limit 10"));
|
||||
}
|
||||
}
|
||||
@@ -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.neo4j.repository.query;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.springframework.data.neo4j.annotation.Indexed;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelatedTo;
|
||||
|
||||
@NodeEntity
|
||||
class Group {
|
||||
|
||||
@Indexed
|
||||
String name;
|
||||
|
||||
@RelatedTo(type = "members", direction = Direction.OUTGOING)
|
||||
Set<Person> members;
|
||||
}
|
||||
@@ -20,19 +20,15 @@ import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.neo4j.annotation.Indexed;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelatedTo;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MatchClause}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@@ -40,51 +36,36 @@ import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
public class MatchClauseUnitTest {
|
||||
|
||||
Neo4jMappingContext context;
|
||||
private VariableContext variableContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = new Neo4jMappingContext();
|
||||
context.setInitialEntitySet(Collections.singleton(Person.class));
|
||||
context.afterPropertiesSet();
|
||||
variableContext = new VariableContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildsMatchExpressionForSimpleTraversalCorrectly() {
|
||||
|
||||
MatchClause clause = new MatchClause(context, PropertyPath.from("group", Person.class));
|
||||
assertThat(clause.toString(), is("(person)<-[:members]-(group)"));
|
||||
PropertyPath path = PropertyPath.from("group", Person.class);
|
||||
MatchClause clause = new MatchClause(context.getPersistentPropertyPath(path));
|
||||
assertThat(clause.toString(variableContext), is("person<-[:members]-person_group"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createsMatchClassForDeepTraversal() {
|
||||
|
||||
MatchClause clause = new MatchClause(context, PropertyPath.from("group.members.age", Person.class));
|
||||
assertThat(clause.toString(), is("(person)<-[:members]-(group)-[:members]->(members)"));
|
||||
PropertyPath path = PropertyPath.from("group.members.age", Person.class);
|
||||
MatchClause clause = new MatchClause(context.getPersistentPropertyPath(path));
|
||||
assertThat(clause.toString(variableContext), is("person<-[:members]-person_group-[:members]->person_group_members"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stopsAtNonRelationShipPropertyPath() {
|
||||
|
||||
MatchClause clause = new MatchClause(context, PropertyPath.from("group.name", Person.class));
|
||||
assertThat(clause.toString(), is("(person)<-[:members]-(group)"));
|
||||
}
|
||||
|
||||
@NodeEntity
|
||||
class Person {
|
||||
|
||||
private int age;
|
||||
|
||||
@RelatedTo(type = "members", direction = Direction.INCOMING)
|
||||
private Group group;
|
||||
}
|
||||
|
||||
@NodeEntity
|
||||
class Group {
|
||||
|
||||
@Indexed
|
||||
private String name;
|
||||
|
||||
@RelatedTo(type = "members", direction = Direction.OUTGOING)
|
||||
private Set<Person> members;
|
||||
PropertyPath path = PropertyPath.from("group.name", Person.class);
|
||||
MatchClause clause = new MatchClause(context.getPersistentPropertyPath(path));
|
||||
assertThat(clause.toString(variableContext), is("person<-[:members]-person_group"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.neo4j.repository.query;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.springframework.data.neo4j.annotation.Indexed;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelatedTo;
|
||||
|
||||
@NodeEntity
|
||||
class Person {
|
||||
|
||||
@Indexed
|
||||
String name;
|
||||
int age;
|
||||
|
||||
@RelatedTo(type = "members", direction = Direction.INCOMING)
|
||||
Group group;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.repository.query;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link VariableContext}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class VariableContextUnitTests {
|
||||
|
||||
Neo4jMappingContext mappingContext;
|
||||
VariableContext context;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
mappingContext = new Neo4jMappingContext();
|
||||
context = new VariableContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameForSimplePropertyIsOwner() {
|
||||
assertThat(context.getVariableFor(getPath("age")), is("person"));
|
||||
}
|
||||
@Test
|
||||
public void nameForPathViaEntityIsOwnerAndEntity() {
|
||||
assertThat(context.getVariableFor(getPath("group.members")), is("person_group_members"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nameForEntityIsLowercaseSimpleClassName() {
|
||||
assertThat(context.getVariableFor(mappingContext.getPersistentEntity(Person.class)),is("person"));
|
||||
}
|
||||
@Test
|
||||
public void nameForEntityPropertyIsOwnerAndEntity() {
|
||||
final PersistentPropertyPath<Neo4jPersistentProperty> gropPath = getPath("group");
|
||||
assertThat(context.getVariableFor(gropPath),is("person_group"));
|
||||
}
|
||||
|
||||
private PersistentPropertyPath<Neo4jPersistentProperty> getPath(String expression) {
|
||||
|
||||
PropertyPath path = PropertyPath.from(expression, Person.class);
|
||||
return mappingContext.getPersistentPropertyPath(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
|
||||
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
|
||||
|
||||
<context:annotation-config/>
|
||||
<neo4j:config graphDatabaseService="graphDatabaseService"/>
|
||||
<neo4j:repositories base-package="org.springframework.data.neo4j.repository"/>
|
||||
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown"/>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user