diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4JPersistentPropertyImpl.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4JPersistentPropertyImpl.java index be5bcece2..88e56872e 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4JPersistentPropertyImpl.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/Neo4JPersistentPropertyImpl.java @@ -52,7 +52,7 @@ class Neo4jPersistentPropertyImpl extends AbstractPersistentProperty declaringClass = property.getField().getDeclaringClass(); + final Class instanceType = property.getOwner().getType(); + return Indexed.Name.get(annotation.level(), declaringClass, providedIndexName, instanceType); + } + public String getIndexName() { return indexName; } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java index cb7cec0df..2ac07ed3c 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactory.java @@ -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, 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, 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 EntityInformation getEntityInformation(Class 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 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(graphDatabaseContext)); } + @Override protected Object dispatchQuery(String queryString, Map 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); } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java index e69d7d02b..68fd9d350 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/GraphRepositoryFactoryBean.java @@ -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, T> - extends TransactionalRepositoryFactoryBeanSupport { +public class GraphRepositoryFactoryBean, T> extends +TransactionalRepositoryFactoryBeanSupport { private GraphDatabaseContext graphDatabaseContext; + private MappingContext, Neo4jPersistentProperty> mappingContext; public void setGraphDatabaseContext(GraphDatabaseContext graphDatabaseContext) { this.graphDatabaseContext = graphDatabaseContext; } + /** + * @param mappingContext the mappingContext to set + */ + public void setMappingContext( + MappingContext, 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(); } } \ No newline at end of file diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQuery.java deleted file mode 100644 index aa4f002d6..000000000 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQuery.java +++ /dev/null @@ -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 matchClauses; - - public CypherQuery(TypeInformation rootType, Neo4jMappingContext context) { - - Assert.notNull(rootType); - Assert.notNull(context); - - this.rootType = rootType; - this.context = context; - this.matchClauses = new HashSet(); - } - - 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"); - } -} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java new file mode 100644 index 000000000..0357884a0 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilder.java @@ -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, Neo4jPersistentProperty> context; + + private final VariableContext variableContext = new VariableContext(); + private final List matchClauses = new ArrayList(); + private final List startClauses = new ArrayList(); + private final List whereClauses = new ArrayList(); + + 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, 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 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 sorts = formatSorts(sort); + return !sorts.isEmpty() ? String + .format(QueryTemplates.ORDER_BY_CLAUSE, collectionToCommaDelimitedString(sorts)) : ""; + } + + private List formatSorts(Sort sort) { + List result = new ArrayList(); + 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 matchClauses) { + List result = new ArrayList(matchClauses.size()); + for (MatchClause matchClause : matchClauses) { + result.add(matchClause.toString(variableContext)); + } + return collectionToDelimitedString(result, ", "); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java new file mode 100644 index 000000000..bdf8047e8 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryCreator.java @@ -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 { + + private final MappingContext, 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, 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 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 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; + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryDefinition.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryDefinition.java new file mode 100644 index 000000000..0188d49d5 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/CypherQueryDefinition.java @@ -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); +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/DerivedCypherRepositoryQuery.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/DerivedCypherRepositoryQuery.java new file mode 100644 index 000000000..aca428e41 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/DerivedCypherRepositoryQuery.java @@ -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, 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 paramMap = new HashMap(); + 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(); + } + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java index 770c96ccd..9f6ef77fa 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/MatchClause.java @@ -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 properties; + private final PersistentPropertyPath 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 path) { + Assert.notNull(path); + this.path = relationshipPath(path); } - /* - * (non-Javadoc) - * @see java.lang.Object#toString() + private PersistentPropertyPath relationshipPath( + PersistentPropertyPath 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 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); } } \ No newline at end of file diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java new file mode 100644 index 000000000..cfaa321f4 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/QueryTemplates.java @@ -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() { + + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java index 8d49b22b8..df739e281 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/StartClause.java @@ -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 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 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); + } } \ No newline at end of file diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/VariableContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/VariableContext.java new file mode 100644 index 000000000..bf88d7f95 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/VariableContext.java @@ -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, String> variables; + + public VariableContext() { + this.variables = new HashMap, String>(); + } + + public String getVariableFor(PersistentPropertyPath path) { + + if (variables.containsKey(path)) { + return variables.get(path); + } + + Neo4jPersistentProperty baseProperty = path.getBaseProperty(); + List parts = new ArrayList(); + 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()); + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java new file mode 100644 index 000000000..6880eaec0 --- /dev/null +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/WhereClause.java @@ -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 SYMBOLS; + + static { + + Map symbols = new HashMap(); + 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 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 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); + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java index c67407dc0..19587bf49 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Group.java @@ -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 persons; + private Collection persons = new HashSet(); @RelatedTo(type = "persons", elementClass = Person.class) private Iterable 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); + } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/PersonRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/PersonRepository.java index f18fc8838..a4749a607 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/PersonRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/PersonRepository.java @@ -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 diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java new file mode 100644 index 000000000..a446cf0d0 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GraphRepositoryTest.java @@ -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 teamMembers = personRepository.findAllTeamMembers(testTeam.sdg); + assertThat(asCollection(teamMembers), hasItems(testTeam.michael, testTeam.david, testTeam.emil)); + } + @Test + public void testFindIterableOfPersonWithQueryAnnotationAndGremlin() { + Iterable 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> 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 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 teamMemberPage1 = personRepository.findAllTeamMembersPaged(testTeam.sdg,page); + assertEquals(asList(testTeam.michael, testTeam.emil), asCollection(teamMemberPage1)); + assertThat(teamMemberPage1.isFirstPage(), is(true)); + } + @Test + public void testFindPagedNull() { + Page 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 teamMembers = personRepository.findAllTeamMembersSorted(testTeam.sdg, sort); + assertEquals(asList(testTeam.michael, testTeam.emil, testTeam.david), asCollection(teamMembers)); + } + + @Test + public void testFindSortedNull() { + Iterable 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 findByName = personRepository.findByName(testTeam.michael.getName()); + assertThat(findByName, hasItem(testTeam.michael)); + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GroupRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GroupRepository.java new file mode 100644 index 000000000..84f881f15 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/GroupRepository.java @@ -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, NamedIndexRepository { +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/PersonRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/PersonRepository.java new file mode 100644 index 000000000..6b64074e8 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/PersonRepository.java @@ -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, NamedIndexRepository { + + @Query("start team=node({p_team}) match (team)-[:persons]->(member) return member") + Iterable findAllTeamMembers(@Param("p_team") Group team); + + @Query(value = "g.v(team).out('persons')", type = QueryType.Gremlin) + Iterable findAllTeamMembersGremlin(@Param("team") Group team); + + @Query("start team=node({p_team}) match (team)-[:persons]->(member) return member.name,member.age") + Iterable> 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 findAllTeamMembersPaged(@Param("p_team") Group team, Pageable page); + + @Query("start team=node({p_team}) match (team)-[:persons]->(member) return member") + Iterable findAllTeamMembersSorted(@Param("p_team") Group team, Sort sort); + + // Derived queries + Iterable findByName(String name); +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/TestTeam.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/TestTeam.java new file mode 100644 index 000000000..ae622925c --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/TestTeam.java @@ -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 simpleRowFor(final Person person, String prefix) { + return MapUtil.map(prefix+".name", person.getName(), prefix+".age", person.getAge()); + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderUnitTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderUnitTests.java new file mode 100644 index 000000000..1828258ff --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/CypherQueryBuilderUnitTests.java @@ -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")); + } +} diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Group.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Group.java new file mode 100644 index 000000000..122f97d84 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Group.java @@ -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 members; +} \ No newline at end of file diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/MatchClauseUnitTest.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/MatchClauseUnitTest.java index 74a3a89b6..2fbc291d1 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/MatchClauseUnitTest.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/MatchClauseUnitTest.java @@ -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 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")); } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Person.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Person.java new file mode 100644 index 000000000..5f65329e8 --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/Person.java @@ -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; +} \ No newline at end of file diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/VariableContextUnitTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/VariableContextUnitTests.java new file mode 100644 index 000000000..64f3e0f1a --- /dev/null +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/VariableContextUnitTests.java @@ -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 gropPath = getPath("group"); + assertThat(context.getVariableFor(gropPath),is("person_group")); + } + + private PersistentPropertyPath getPath(String expression) { + + PropertyPath path = PropertyPath.from(expression, Person.class); + return mappingContext.getPersistentPropertyPath(path); + } +} diff --git a/spring-data-neo4j/src/test/resources/org/springframework/data/neo4j/repository/GraphRepositoryTest-context.xml b/spring-data-neo4j/src/test/resources/org/springframework/data/neo4j/repository/GraphRepositoryTest-context.xml new file mode 100644 index 000000000..6796328ff --- /dev/null +++ b/spring-data-neo4j/src/test/resources/org/springframework/data/neo4j/repository/GraphRepositoryTest-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + \ No newline at end of file