Initial draft of automatic Cypher query creation for repositories.

This commit is contained in:
Oliver Gierke
2011-06-18 08:07:02 +02:00
parent d8e9925467
commit 5656f08461
10 changed files with 542 additions and 15 deletions

View File

@@ -20,29 +20,34 @@ import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.mapping.context.AbstractMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.util.TypeInformation;
/**
* Neo4J specific {@link MappingContext} implementation. Simply creates {@link Neo4JPersistentEntityImpl} and
* {@link Neo4JPersistentProperty} instances.
*
* @author Oliver Gierke
*/
public class Neo4JMappingContext extends AbstractMappingContext<Neo4JPersistentEntityImpl<?>, Neo4JPersistentProperty> {
/* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
*/
@Override
protected <T> Neo4JPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
return new Neo4JPersistentEntityImpl<T>(typeInformation);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
*/
@Override
protected <T> Neo4JPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
return new Neo4JPersistentEntityImpl<T>(typeInformation);
}
/* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder)
*/
@Override
protected Neo4JPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
Neo4JPersistentEntityImpl<?> owner, SimpleTypeHolder simpleTypeHolder) {
return new Neo4JPersistentPropertyImpl(field, descriptor, owner, simpleTypeHolder);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder)
*/
@Override
protected Neo4JPersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
Neo4JPersistentEntityImpl<?> owner, SimpleTypeHolder simpleTypeHolder) {
return new Neo4JPersistentPropertyImpl(field, descriptor, owner, simpleTypeHolder);
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.mapping;
import org.springframework.data.mapping.PersistentEntity;
/**
* Interface for Neo4J specific {@link PersistentEntity}.
*
* @author Oliver Gierke
*/
public interface Neo4JPersistentEntity<T> extends PersistentEntity<T, Neo4JPersistentProperty> {
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.mapping;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
/**
* Implementation of {@link Neo4JPersistentEntity}.
*
* @author Oliver Gierke
*/
class Neo4JPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4JPersistentProperty> implements Neo4JPersistentEntity<T> {
/**
* Creates a new {@link Neo4JPersistentEntityImpl} instance.
*
* @param information must not be {@literal null}.
*/
public Neo4JPersistentEntityImpl(TypeInformation<T> information) {
super(information);
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.mapping;
import org.springframework.data.mapping.PersistentProperty;
/**
* Interface for Neo4J specific {@link PersistentProperty}s. Declares additional metadata to lookup relationship
* information.
*
* @author Oliver Gierke
*/
public interface Neo4JPersistentProperty extends PersistentProperty<Neo4JPersistentProperty> {
/**
* Returns whether the property represents a relationship. If this returns {@literal true}, clients can expect
* {@link #getRelationshipInfo()} to return a non-{@literal null} value.
*
* @return
*/
boolean isRelationship();
/**
* Returns the {@link RelationshipInfo} for the given property if it is a relationship or {@literal null} otherwise.
*
* @see #isRelationship()
* @return
*/
RelationshipInfo getRelationshipInfo();
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.mapping;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import org.springframework.data.graph.annotation.GraphId;
import org.springframework.data.graph.annotation.RelatedTo;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AbstractPersistentProperty;
import org.springframework.data.mapping.model.SimpleTypeHolder;
/**
* Implementation of {@link Neo4JPersistentProperty}.
*
* @author Oliver Gierke
*/
class Neo4JPersistentPropertyImpl extends AbstractPersistentProperty<Neo4JPersistentProperty> implements
Neo4JPersistentProperty {
private final RelationshipInfo relationshipInfo;
private final boolean isIdProperty;
/**
* Creates a new {@link Neo4JPersistentPropertyImpl} from the given {@link Field}, {@link PropertyDescriptor} owner
* {@link PersistentEntity} and {@link SimpleTypeHolder}.
*
* @param field
* @param propertyDescriptor
* @param owner
* @param simpleTypeHolder
*/
public Neo4JPersistentPropertyImpl(Field field, PropertyDescriptor propertyDescriptor,
PersistentEntity<?, Neo4JPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
super(field, propertyDescriptor, owner, simpleTypeHolder);
this.relationshipInfo = field.isAnnotationPresent(RelatedTo.class) ? new AnnotationBasedRelationshipInfo(
field.getAnnotation(RelatedTo.class)) : null;
this.isIdProperty = field.isAnnotationPresent(GraphId.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.PersistentProperty#isIdProperty()
*/
@Override
public boolean isIdProperty() {
return this.isIdProperty;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
*/
@Override
protected Association<Neo4JPersistentProperty> createAssociation() {
return new Association<Neo4JPersistentProperty>(this, null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.graph.neo4j.mapping.Neo4JPersistentProperty#isRelationship()
*/
@Override
public boolean isRelationship() {
return this.relationshipInfo != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.graph.neo4j.mapping.Neo4JPersistentProperty#getRelationShipInfo()
*/
@Override
public RelationshipInfo getRelationshipInfo() {
return relationshipInfo;
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.mapping;
import org.springframework.data.graph.core.Direction;
/**
* Captures information about a relationship.
*
* @author Oliver Gierke
*/
public interface RelationshipInfo {
/**
* Returns the direction of the relationship.
*
* @return
*/
Direction getDirection();
/**
* Returns the type of the relationship.
*
* @return
*/
String getType();
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.repository.query;
import java.util.HashSet;
import java.util.Set;
import org.springframework.data.graph.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");
}
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.repository.query;
import org.springframework.data.graph.neo4j.mapping.Neo4JMappingContext;
import org.springframework.data.graph.neo4j.mapping.Neo4JPersistentProperty;
import org.springframework.data.graph.neo4j.mapping.RelationshipInfo;
import org.springframework.data.repository.query.parser.Property;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Value object to build the {@code match} clause of a Cypher query.
*
* @author Oliver Gierke
*/
class MatchClause {
private final Iterable<Neo4JPersistentProperty> properties;
/**
* Creates a new {@link MatchClause} using the given {@link Neo4JMappingContext} and {@link Property}.
*
* @param context must not be {@literal null}.
* @param property must not be {@literal null}.
*/
public MatchClause(Neo4JMappingContext context, Property property) {
Assert.notNull(context);
Assert.notNull(property);
Class<?> rootType = property.getOwningType().getType();
this.properties = context.getPersistentPropertyPath(rootType, property.toDotPath());
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
String intermediate = null;
for (Neo4JPersistentProperty property : properties) {
if (!property.isRelationship()) {
return intermediate;
}
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();
}
/**
* Returns the given value as variable reference.
*
* @param value
* @return
*/
private static String asVariableReference(String value) {
return String.format("(%s)", value);
}
/**
* 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!");
}
}
}

View File

@@ -0,0 +1,21 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.repository.query;
class StartClause {
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.repository.query;
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.Test;
import org.springframework.data.graph.annotation.NodeEntity;
import org.springframework.data.graph.annotation.RelatedTo;
import org.springframework.data.graph.core.Direction;
import org.springframework.data.graph.neo4j.annotation.Indexed;
import org.springframework.data.graph.neo4j.mapping.Neo4JMappingContext;
import org.springframework.data.repository.query.parser.Property;
/**
*
* @author Oliver Gierke
*/
public class MatchClauseUnitTest {
Neo4JMappingContext context;
@Before
public void setUp() {
context = new Neo4JMappingContext();
context.setInitialEntitySet(Collections.singleton(Person.class));
context.afterPropertiesSet();
}
@Test
public void buildsMatchExpressionForSimpleTraversalCorrectly() {
MatchClause clause = new MatchClause(context, Property.from("group", Person.class));
assertThat(clause.toString(), is("(person)<-[:members]-(group)"));
}
@Test
public void createsMatchClassForDeepTraversal() {
MatchClause clause = new MatchClause(context, Property.from("group.members.age", Person.class));
assertThat(clause.toString(), is("(person)<-[:members]-(group)-[:members]->(members)"));
}
@Test
public void stopsAtNonRelationShipProperty() {
MatchClause clause = new MatchClause(context, Property.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;
}
class Group {
@Indexed
private String name;
@RelatedTo(type = "members", direction = Direction.OUTGOING)
private Set<Person> members;
}
}