DATAGRAPH-388 Support for Labels (indexes, queries etc)
* @Indexed(indexType=LABEL) * added template.isLabelBased(), indexInfo.isLabelBased() and TRS.isLabelBased() * already reworked some of the derived finders to consistently use a start-less syntax wherever possible * first stab at dynamic Labels with @Labels and LabelFieldAccessorFactory
This commit is contained in:
@@ -45,6 +45,7 @@ import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public abstract class AbstractNodeTypeRepresentationStrategyTestBase extends EntityTestBase {
|
||||
|
||||
@@ -96,7 +97,12 @@ public abstract class AbstractNodeTypeRepresentationStrategyTestBase extends Ent
|
||||
IteratorUtil.addToCollection(allThings, new HashSet<Node>()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test
|
||||
public void testAssertLabelIndexOrNot() throws Exception {
|
||||
assertFalse("not label based", nodeTypeRepresentationStrategy.isLabelBased());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Transactional
|
||||
public void testCountOfSuperTypeIncludesSubTypes() throws Exception {
|
||||
final int EXPECTED_NUM_THINGS = 1;
|
||||
|
||||
@@ -48,6 +48,7 @@ import java.util.HashSet;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
@@ -76,7 +77,7 @@ public class IndexBasedNodeTypeRepresentationStrategyTests extends AbstractNodeT
|
||||
instanceOf(IndexBasedNodeTypeRepresentationStrategy.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test
|
||||
@Transactional
|
||||
@Override
|
||||
public void testPostEntityCreation() throws Exception {
|
||||
|
||||
@@ -30,6 +30,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests to ensure that all scenarios involved in entity creation / reading etc
|
||||
@@ -69,5 +71,9 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends AbstractNodeT
|
||||
// preEntityRemoval is a no op method, so nothing to test here!
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testAssertLabelIndexOrNot() throws Exception {
|
||||
assertTrue("label based", nodeTypeRepresentationStrategy.isLabelBased());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
<bean id="conversionService" class="org.springframework.data.neo4j.fieldaccess.Neo4jConversionServiceFactoryBean"/>
|
||||
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.IndexCreationMappingEventListener">
|
||||
<constructor-arg ref="indexProvider" />
|
||||
<constructor-arg ref="schemaIndexProvider" />
|
||||
</bean>
|
||||
|
||||
|
||||
@@ -60,6 +61,9 @@
|
||||
<bean id="indexProvider" class="org.springframework.data.neo4j.support.index.IndexProviderImpl">
|
||||
<constructor-arg ref="graphDatabase" />
|
||||
</bean>
|
||||
<bean id="schemaIndexProvider" class="org.springframework.data.neo4j.support.schema.SchemaIndexProvider">
|
||||
<constructor-arg ref="graphDatabase" />
|
||||
</bean>
|
||||
|
||||
<bean id="entityStateHandler" class="org.springframework.data.neo4j.support.mapping.EntityStateHandler">
|
||||
<constructor-arg ref="mappingContext"/>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to explcitely declare a property handled by datastore-graph. Automatically indexes the property.
|
||||
* Only required in partial mode. Otherwise properties are handled by default if they are primitive or convertible to
|
||||
* a String using the built in conversion services.
|
||||
*
|
||||
* @author Michael Hunger
|
||||
* @since 27.08.2010
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
|
||||
public @interface Labels {
|
||||
String[] defaultValue() default {};
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import org.springframework.data.neo4j.support.node.NodeEntityInstantiator;
|
||||
import org.springframework.data.neo4j.support.node.NodeEntityStateFactory;
|
||||
import org.springframework.data.neo4j.support.relationship.RelationshipEntityInstantiator;
|
||||
import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory;
|
||||
import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.ClassValueTypeInformationMapper;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
|
||||
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
|
||||
@@ -238,7 +239,7 @@ public abstract class Neo4jConfiguration {
|
||||
|
||||
@Bean
|
||||
public IndexCreationMappingEventListener indexCreationMappingEventListener() throws Exception {
|
||||
return new IndexCreationMappingEventListener(indexProvider());
|
||||
return new IndexCreationMappingEventListener(indexProvider(),schemaIndexProvider());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -264,6 +265,11 @@ public abstract class Neo4jConfiguration {
|
||||
return new IndexProviderImpl(graphDatabase());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SchemaIndexProvider schemaIndexProvider() throws Exception {
|
||||
return new SchemaIndexProvider(graphDatabase());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TypeSafetyPolicy typeSafetyPolicy() throws Exception {
|
||||
return new TypeSafetyPolicy();
|
||||
|
||||
@@ -70,4 +70,6 @@ public interface TypeRepresentationStrategy<S extends PropertyContainer> {
|
||||
* @param state Backing state of entity being removed
|
||||
*/
|
||||
void preEntityRemoval(S state);
|
||||
|
||||
boolean isLabelBased();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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.fieldaccess;
|
||||
|
||||
|
||||
import org.neo4j.graphdb.*;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.neo4j.annotation.Labels;
|
||||
import org.springframework.data.neo4j.mapping.MappingPolicy;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.springframework.data.neo4j.support.DoReturn.doReturn;
|
||||
|
||||
/**
|
||||
* @author Michael Hunger
|
||||
* @since 12.09.2010
|
||||
*/
|
||||
public class LabelFieldAccessorFactory implements FieldAccessorFactory {
|
||||
private final Neo4jTemplate template;
|
||||
|
||||
public LabelFieldAccessorFactory(Neo4jTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(final Neo4jPersistentProperty property) {
|
||||
return property.isAnnotationPresent(Labels.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldAccessor forField(final Neo4jPersistentProperty property) {
|
||||
return new LabelFieldAccessor(property, template);
|
||||
}
|
||||
|
||||
public static class LabelFieldAccessor implements FieldAccessor {
|
||||
protected final Neo4jPersistentProperty property;
|
||||
private final Neo4jTemplate template;
|
||||
|
||||
public LabelFieldAccessor(final Neo4jPersistentProperty property, Neo4jTemplate template) {
|
||||
this.property = property;
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWriteable(Object entity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setValue(final Object entity, final Object newVal, MappingPolicy mappingPolicy) {
|
||||
if (entity==null) return entity;
|
||||
final PropertyContainer state = template.getPersistentState(entity);
|
||||
if (state instanceof Node) {
|
||||
Node node = (Node) state;
|
||||
Set<String> oldLabels = getLabels(node);
|
||||
for (String newLabel : (Iterable<String>) entity) {
|
||||
if (oldLabels.remove(newLabel)) continue;
|
||||
node.addLabel(DynamicLabel.label(newLabel));
|
||||
}
|
||||
for (String removedLabels : oldLabels) {
|
||||
node.removeLabel(DynamicLabel.label(removedLabels));
|
||||
}
|
||||
return doReturn(newVal);
|
||||
}
|
||||
throw new MappingException("Error setting labels on "+entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(final Object entity, MappingPolicy mappingPolicy) {
|
||||
final PropertyContainer state = template.getPersistentState(entity);
|
||||
if (state instanceof Node) {
|
||||
return doReturn(getLabels((Node) state));
|
||||
}
|
||||
throw new MappingException("Error retrieving labels from "+entity);
|
||||
}
|
||||
|
||||
private Set<String> getLabels(Node state) {
|
||||
Set<String> labels = new TreeSet<>();
|
||||
for (Label label : state.getLabels()) {
|
||||
labels.add(label.name());
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDefaultValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ public class NodeDelegatingFieldAccessorFactory extends DelegatingFieldAccessorF
|
||||
return Arrays.<FieldAccessorFactory>asList(
|
||||
new IdFieldAccessorFactory(template),
|
||||
new TransientFieldAccessorFactory(),
|
||||
//TODO Labels new LabelFieldAccessorFactory(template),
|
||||
new TraversalFieldAccessorFactory(template),
|
||||
new QueryFieldAccessorFactory(template),
|
||||
new PropertyFieldAccessorFactory(template),
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.mapping;
|
||||
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.neo4j.annotation.Indexed;
|
||||
import org.springframework.data.neo4j.support.index.IndexType;
|
||||
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
@@ -32,13 +34,36 @@ public class IndexInfo {
|
||||
private boolean numeric;
|
||||
|
||||
public IndexInfo(Indexed annotation, Neo4jPersistentProperty property) {
|
||||
this.indexName = determineIndexName(annotation, property);
|
||||
this.indexType = annotation.indexType();
|
||||
this.indexName = isLabelBased() ? determineLabelIndexName(annotation, property) : determineIndexName(annotation, property);
|
||||
fieldName = annotation.fieldName();
|
||||
this.indexKey = fieldName.isEmpty() ? property.getNeo4jPropertyName() : fieldName;
|
||||
unique = annotation.unique();
|
||||
level = annotation.level();
|
||||
numeric = annotation.numeric();
|
||||
verify(property);
|
||||
}
|
||||
|
||||
private void verify(Neo4jPersistentProperty property) {
|
||||
if (isLabelBased() && numeric) {
|
||||
throw new MappingException("No numeric indexing and range queries currently supported for label based indexes, property: " + property.getOwner().getName()+"."+property.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private String determineLabelIndexName(Indexed annotation, Neo4jPersistentProperty property) {
|
||||
if (!annotation.indexName().isEmpty()) throw new MappingException("No index name allowed on label based indexes");
|
||||
Neo4jPersistentEntity<?> entity = property.getOwner();
|
||||
StoredEntityType entityType = entity.getEntityType();
|
||||
switch (annotation.level()) {
|
||||
case CLASS:
|
||||
Class<?> declaringClass = property.getField().getDeclaringClass();
|
||||
StoredEntityType classType = entityType.findByTypeClass(declaringClass);
|
||||
return classType.getAlias().toString();
|
||||
case INSTANCE:
|
||||
return entityType.getAlias().toString();
|
||||
case GLOBAL: throw new MappingException("No global index for label based indexes");
|
||||
}
|
||||
return entityType.getAlias().toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +74,10 @@ public class IndexInfo {
|
||||
return Indexed.Name.get(annotation.level(), declaringClass, providedIndexName, instanceType);
|
||||
}
|
||||
|
||||
public boolean isLabelBased() {
|
||||
return indexType.isLabelBased();
|
||||
}
|
||||
|
||||
public String getIndexName() {
|
||||
return indexName;
|
||||
}
|
||||
|
||||
@@ -16,15 +16,12 @@
|
||||
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
import org.apache.lucene.search.NumericRangeQuery;
|
||||
import org.neo4j.cypherdsl.grammar.Execute;
|
||||
import org.neo4j.cypherdsl.grammar.Skip;
|
||||
import org.neo4j.graphdb.NotFoundException;
|
||||
import org.neo4j.graphdb.PropertyContainer;
|
||||
import org.neo4j.graphdb.index.IndexHits;
|
||||
import org.neo4j.graphdb.index.ReadableIndex;
|
||||
import org.neo4j.helpers.collection.ClosableIterable;
|
||||
import org.neo4j.helpers.collection.IterableWrapper;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
@@ -32,15 +29,9 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.conversion.EndResult;
|
||||
import org.springframework.data.neo4j.conversion.Result;
|
||||
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.repository.query.CypherQuery;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
|
||||
import org.springframework.data.neo4j.support.index.NullReadableIndex;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
@@ -57,6 +48,7 @@ import static org.neo4j.helpers.collection.MapUtil.map;
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public abstract class AbstractGraphRepository<S extends PropertyContainer, T> implements GraphRepository<T>, NamedIndexRepository<T>, SpatialRepository<T>, CypherDslRepository<T> {
|
||||
private final LegacyIndexSearcher<S,T> legacyIndexSearcher;
|
||||
|
||||
/*
|
||||
index.query( LayerNodeIndex.WITHIN_WKT_GEOMETRY_QUERY,
|
||||
@@ -70,22 +62,25 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
|
||||
@Override
|
||||
public EndResult<T> findWithinWellKnownText( final String indexName, String wellKnownText) {
|
||||
return geoQuery(indexName, "withinWKTGeometry", wellKnownText);
|
||||
return legacyIndexSearcher.geoQuery(indexName, "withinWKTGeometry", wellKnownText);
|
||||
}
|
||||
@Override
|
||||
public EndResult<T> findWithinDistance( final String indexName, final double lat, double lon, double distanceKm) {
|
||||
return geoQuery(indexName, "withinDistance", map("point", new Double[] { lon, lat}, "distanceInKm", distanceKm));
|
||||
return legacyIndexSearcher.geoQuery(indexName, "withinDistance", map("point", new Double[] { lon, lat}, "distanceInKm", distanceKm));
|
||||
}
|
||||
|
||||
@Override
|
||||
public EndResult<T> findWithinBoundingBox(final String indexName, final double lowerLeftLat,
|
||||
final double lowerLeftLon, final double upperRightLat, final double upperRightLon) {
|
||||
return geoQuery(indexName, "bbox", format("[%s, %s, %s, %s]", lowerLeftLon, upperRightLon, lowerLeftLat, upperRightLat));
|
||||
return legacyIndexSearcher.geoQuery(indexName, "bbox", format("[%s, %s, %s, %s]", lowerLeftLon, upperRightLon, lowerLeftLat, upperRightLat));
|
||||
}
|
||||
|
||||
private Result<T> geoQuery(String indexName, String geoQuery, Object params) {
|
||||
final IndexHits<S> indexHits = getIndex(indexName,null).query(geoQuery, params);
|
||||
return template.convert(new IndexHitsWrapper(indexHits));
|
||||
interface Query<S extends PropertyContainer> {
|
||||
IndexHits<S> query(ReadableIndex<S> index);
|
||||
}
|
||||
|
||||
protected T createEntity(S node) {
|
||||
return template.createEntityFromState(node, clazz, template.getMappingPolicy(clazz));
|
||||
}
|
||||
|
||||
public static final ClosableIterable EMPTY_CLOSABLE_ITERABLE = new ClosableIterable() {
|
||||
@@ -107,6 +102,7 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
public AbstractGraphRepository(final Neo4jTemplate template, final Class<T> clazz) {
|
||||
this.template = template;
|
||||
this.clazz = clazz;
|
||||
legacyIndexSearcher = new LegacyIndexSearcher<>(template,clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -175,40 +171,10 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
*/
|
||||
@Override
|
||||
public T findByPropertyValue(final String indexName, final String property, final Object value) {
|
||||
try {
|
||||
S result = getIndexHits(indexName, property, value).getSingle();
|
||||
if (result == null) return null;
|
||||
return createEntity(result);
|
||||
} catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
return legacyIndexSearcher.findByPropertyValue(indexName, property, value);
|
||||
|
||||
}
|
||||
|
||||
private IndexHits<S> getIndexHits(String indexName, String propertyName, Object value) {
|
||||
final Neo4jPersistentProperty property = template.getPersistentProperty(clazz, propertyName);
|
||||
if (value instanceof Number && (property==null || property.getIndexInfo().isNumeric())) {
|
||||
Number number = (Number) value;
|
||||
return getIndex(indexName, propertyName).query(propertyName, createInclusiveRangeQuery(propertyName, number,number));
|
||||
}
|
||||
return getIndex(indexName, propertyName).get(propertyName, value);
|
||||
}
|
||||
|
||||
protected ReadableIndex<S> getIndex(String indexName, String property) {
|
||||
try {
|
||||
if (indexName!=null) {
|
||||
return template.getIndex(indexName,clazz);
|
||||
}
|
||||
return template.getIndex(clazz,property);
|
||||
} catch(NoSuchIndexException nsie) {
|
||||
return new NullReadableIndex<S>(nsie.getIndex(),template.getGraphDatabaseService());
|
||||
}
|
||||
}
|
||||
|
||||
protected T createEntity(S node) {
|
||||
return template.createEntityFromState(node, clazz, template.getMappingPolicy(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Index based exact finder.
|
||||
*
|
||||
@@ -219,12 +185,9 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
*/
|
||||
@Override
|
||||
public EndResult<T> findAllByPropertyValue(final String indexName, final String property, final Object value) {
|
||||
return queryResult(indexName, new Query<S>() {
|
||||
public IndexHits<S> query(ReadableIndex<S> index) {
|
||||
return getIndexHits(indexName, property, value);
|
||||
}
|
||||
});
|
||||
return legacyIndexSearcher.findAllByPropertyValue(indexName, property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Index based exact finder, uses the default index name for this type (short class name).
|
||||
* @param property
|
||||
@@ -255,39 +218,7 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
*/
|
||||
@Override
|
||||
public EndResult<T> findAllByQuery(final String indexName, final String property, final Object query) {
|
||||
return queryResult(indexName, new Query<S>() {
|
||||
public IndexHits<S> query(ReadableIndex<S> index) {
|
||||
return getIndex(indexName, property).query(property, query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface Query<S extends PropertyContainer> {
|
||||
IndexHits<S> query(ReadableIndex<S> index);
|
||||
}
|
||||
|
||||
private ClosableIterable<T> query(String indexName, Query<S> query) {
|
||||
try {
|
||||
final IndexHits<S> indexHits = query.query(getIndex(indexName, null));
|
||||
if (indexHits == null) return emptyClosableIterable();
|
||||
return new IndexHitsWrapper(indexHits);
|
||||
} catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private EndResult<T> queryResult(String indexName, Query<S> query) {
|
||||
try {
|
||||
final IndexHits<S> indexHits = query.query(getIndex(indexName, null));
|
||||
return template.convert(indexHits).to(clazz);
|
||||
} catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private ClosableIterable<T> emptyClosableIterable() {
|
||||
return EMPTY_CLOSABLE_ITERABLE;
|
||||
return legacyIndexSearcher.findAllByQuery(indexName, property, query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -296,21 +227,9 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
}
|
||||
@Override
|
||||
public EndResult<T> findAllByRange(final String indexName, final String property, final Number from, final Number to) {
|
||||
return queryResult(indexName, new Query<S>() {
|
||||
public IndexHits<S> query(ReadableIndex<S> index) {
|
||||
return index.query(property, createInclusiveRangeQuery(property, from, to));
|
||||
}
|
||||
});
|
||||
return legacyIndexSearcher.findAllByRange(indexName, property, from, to);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Number> NumericRangeQuery<T> createInclusiveRangeQuery(String property, Number from, Number to) {
|
||||
if (from instanceof Long) return (NumericRangeQuery<T>) NumericRangeQuery.newLongRange(property, from.longValue(),to.longValue(),true,true);
|
||||
if (from instanceof Integer) return (NumericRangeQuery<T>) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true);
|
||||
if (from instanceof Double) return (NumericRangeQuery<T>) NumericRangeQuery.newDoubleRange(property, from.doubleValue(), to.doubleValue(), true, true);
|
||||
if (from instanceof Float) return (NumericRangeQuery<T>) NumericRangeQuery.newFloatRange(property, from.floatValue(), to.floatValue(), true, true);
|
||||
return (NumericRangeQuery<T>) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true);
|
||||
}
|
||||
|
||||
protected abstract S getById(long id);
|
||||
|
||||
@@ -356,8 +275,7 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
|
||||
@Override
|
||||
public EndResult<T> findAll(Sort sort) {
|
||||
TypeRepresentationStrategy nodeTypeRepresentationStrategy = template.getInfrastructure().getNodeTypeRepresentationStrategy();
|
||||
CypherQuery cq = new CypherQuery(template.getEntityType(clazz).getEntity(),template,nodeTypeRepresentationStrategy);
|
||||
CypherQuery cq = new CypherQuery(template.getEntityType(clazz).getEntity(),template, template.isLabelBased());
|
||||
return query(cq.toQueryString(sort), Collections.EMPTY_MAP);
|
||||
}
|
||||
|
||||
@@ -427,25 +345,6 @@ public abstract class AbstractGraphRepository<S extends PropertyContainer, T> im
|
||||
return count;
|
||||
}
|
||||
|
||||
private class IndexHitsWrapper extends IterableWrapper<T, S> implements ClosableIterable<T> {
|
||||
private final IndexHits<S> indexHits;
|
||||
|
||||
public IndexHitsWrapper(IndexHits<S> indexHits) {
|
||||
super(indexHits);
|
||||
this.indexHits = indexHits;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected T underlyingObjectToObject(final S result) {
|
||||
return createEntity(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.indexHits.close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Page<T> query(Execute query, Execute countQuery, Map<String, Object> params, Pageable page) {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.springframework.data.neo4j.repository;
|
||||
|
||||
import org.apache.lucene.search.NumericRangeQuery;
|
||||
import org.neo4j.graphdb.NotFoundException;
|
||||
import org.neo4j.graphdb.PropertyContainer;
|
||||
import org.neo4j.graphdb.index.IndexHits;
|
||||
import org.neo4j.graphdb.index.ReadableIndex;
|
||||
import org.neo4j.helpers.collection.ClosableIterable;
|
||||
import org.neo4j.helpers.collection.IterableWrapper;
|
||||
import org.springframework.data.neo4j.conversion.EndResult;
|
||||
import org.springframework.data.neo4j.conversion.Result;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
|
||||
import org.springframework.data.neo4j.support.index.NullReadableIndex;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 01.02.14
|
||||
*/
|
||||
public class LegacyIndexSearcher<S extends PropertyContainer,T> {
|
||||
private final Neo4jTemplate template;
|
||||
private final Class<T> clazz;
|
||||
|
||||
LegacyIndexSearcher(Neo4jTemplate template, Class<T> clazz) {
|
||||
this.template = template;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
public <T> Result<T> geoQuery(String indexName, String geoQuery, Object params) {
|
||||
final IndexHits<S> indexHits = getIndex(indexName,null).query(geoQuery, params);
|
||||
Iterable<T> wrapper = (Iterable<T>) new IndexHitsWrapper(indexHits);
|
||||
return template.convert(wrapper);
|
||||
}
|
||||
|
||||
private ReadableIndex<S> getIndex(String indexName, String property) {
|
||||
try {
|
||||
if (indexName!=null) {
|
||||
return template.getIndex(indexName,clazz);
|
||||
}
|
||||
return template.getIndex(clazz,property);
|
||||
} catch(NoSuchIndexException nsie) {
|
||||
return new NullReadableIndex<S>(nsie.getIndex(),template.getGraphDatabaseService());
|
||||
}
|
||||
}
|
||||
|
||||
private T createEntity(S node) {
|
||||
return template.createEntityFromState(node, clazz, template.getMappingPolicy(clazz));
|
||||
}
|
||||
|
||||
private class IndexHitsWrapper extends IterableWrapper<T, S> implements ClosableIterable<T> {
|
||||
private final IndexHits<S> indexHits;
|
||||
|
||||
public IndexHitsWrapper(IndexHits<S> indexHits) {
|
||||
super(indexHits);
|
||||
this.indexHits = indexHits;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected T underlyingObjectToObject(final S result) {
|
||||
return createEntity(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.indexHits.close();
|
||||
}
|
||||
}
|
||||
|
||||
private IndexHits<S> getIndexHits(String indexName, String propertyName, Object value) {
|
||||
final Neo4jPersistentProperty property = template.getPersistentProperty(clazz, propertyName);
|
||||
if (value instanceof Number && (property==null || property.getIndexInfo().isNumeric())) {
|
||||
Number number = (Number) value;
|
||||
return getIndex(indexName, propertyName).query(propertyName, createInclusiveRangeQuery(propertyName, number,number));
|
||||
}
|
||||
return getIndex(indexName, propertyName).get(propertyName, value);
|
||||
}
|
||||
|
||||
private ClosableIterable<T> query(String indexName, AbstractGraphRepository.Query<S> query) {
|
||||
try {
|
||||
final IndexHits<S> indexHits = query.query(getIndex(indexName, null));
|
||||
if (indexHits == null) return emptyClosableIterable();
|
||||
return new IndexHitsWrapper(indexHits);
|
||||
} catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private ClosableIterable<T> emptyClosableIterable() {
|
||||
return AbstractGraphRepository.EMPTY_CLOSABLE_ITERABLE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T extends Number> NumericRangeQuery<T> createInclusiveRangeQuery(String property, Number from, Number to) {
|
||||
if (from instanceof Long) return (NumericRangeQuery<T>) NumericRangeQuery.newLongRange(property, from.longValue(),to.longValue(),true,true);
|
||||
if (from instanceof Integer) return (NumericRangeQuery<T>) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true);
|
||||
if (from instanceof Double) return (NumericRangeQuery<T>) NumericRangeQuery.newDoubleRange(property, from.doubleValue(), to.doubleValue(), true, true);
|
||||
if (from instanceof Float) return (NumericRangeQuery<T>) NumericRangeQuery.newFloatRange(property, from.floatValue(), to.floatValue(), true, true);
|
||||
return (NumericRangeQuery<T>) NumericRangeQuery.newIntRange(property, from.intValue(), to.intValue(), true, true);
|
||||
}
|
||||
|
||||
public EndResult<T> findAllByRange(String indexName, final String property, final Number from, final Number to) {
|
||||
return queryResult(indexName, new AbstractGraphRepository.Query<S>() {
|
||||
public IndexHits<S> query(ReadableIndex<S> index) {
|
||||
return index.query(property, createInclusiveRangeQuery(property, from, to));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public EndResult<T> findAllByQuery(final String indexName, final String property, final Object query) {
|
||||
return queryResult(indexName, new AbstractGraphRepository.Query<S>() {
|
||||
public IndexHits<S> query(ReadableIndex<S> index) {
|
||||
return getIndex(indexName, property).query(property, query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public T findByPropertyValue(String indexName, String property, Object value) {
|
||||
try {
|
||||
S result = getIndexHits(indexName, property, value).getSingle();
|
||||
if (result == null) return null;
|
||||
return createEntity(result);
|
||||
} catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public EndResult<T> findAllByPropertyValue(final String indexName, final String property, final Object value) {
|
||||
return queryResult(indexName, new AbstractGraphRepository.Query<S>() {
|
||||
public IndexHits<S> query(ReadableIndex<S> index) {
|
||||
return getIndexHits(indexName, property, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private EndResult<T> queryResult(String indexName, AbstractGraphRepository.Query<S> query) {
|
||||
try {
|
||||
final IndexHits<S> indexHits = query.query(getIndex(indexName, null));
|
||||
return template.convert(indexHits).to(clazz);
|
||||
} catch (NotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,9 @@ package org.springframework.data.neo4j.repository.query;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
|
||||
@@ -43,10 +41,10 @@ public class CypherQuery implements CypherQueryDefinition {
|
||||
private boolean isCountQuery = false;
|
||||
private boolean useLabels = false;
|
||||
|
||||
public CypherQuery(final Neo4jPersistentEntity<?> entity, Neo4jTemplate template, TypeRepresentationStrategy nodeTypeRepresentationStrategy) {
|
||||
public CypherQuery(final Neo4jPersistentEntity<?> entity, Neo4jTemplate template, boolean useLabels) {
|
||||
this.entity = entity;
|
||||
this.template = template;
|
||||
this.useLabels = nodeTypeRepresentationStrategy instanceof LabelBasedNodeTypeRepresentationStrategy;
|
||||
this.useLabels = useLabels;
|
||||
}
|
||||
|
||||
private String getEntityName(Neo4jPersistentEntity<?> entity) {
|
||||
@@ -69,19 +67,25 @@ public class CypherQuery implements CypherQueryDefinition {
|
||||
String variable = variableContext.getVariableFor(path);
|
||||
|
||||
final PartInfo partInfo = new PartInfo(path, variable, part, index);
|
||||
MatchClause matchClause = new MatchClause(path);
|
||||
// index("a:foo AND b:bar")
|
||||
// a=index1(a="foo"), b=index2(b="bar") where a=b - not good b/c of cross product
|
||||
// index1(a=foo) where a.foo=bar
|
||||
Neo4jPersistentProperty leafProperty = partInfo.getLeafProperty();
|
||||
if (partInfo.isPrimitiveProperty() && !leafProperty.isIdProperty()) {
|
||||
boolean isIdProperty = leafProperty.isIdProperty();
|
||||
boolean addedMatchClause = false;
|
||||
if (partInfo.isPrimitiveProperty() && !isIdProperty) {
|
||||
if (!addedStartClause(partInfo)) {
|
||||
whereClauses.add(new WhereClause(partInfo,template));
|
||||
}
|
||||
} else if (leafProperty.isRelationship() || leafProperty.isIdProperty()) {
|
||||
startClauses.add(new GraphIdStartClause(partInfo));
|
||||
} else if (leafProperty.isRelationship() || isIdProperty) {
|
||||
if (useLabels) {
|
||||
whereClauses.add(new IdPropertyWhereClause(new PartInfo(path, variable, part, index), template));
|
||||
whereClauses.add(new LabelBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
|
||||
matchClauses.add(matchClause);
|
||||
addedMatchClause = true;
|
||||
} else {
|
||||
startClauses.add(new GraphIdStartClause(partInfo));
|
||||
whereClauses.add(new IndexBasedTypeRestrictingWhereClause(new PartInfo(path, variableContext.getVariableFor(entity), part, -1), entity, template));
|
||||
}
|
||||
} else {
|
||||
@@ -89,9 +93,7 @@ public class CypherQuery implements CypherQueryDefinition {
|
||||
}
|
||||
index += 1;
|
||||
|
||||
MatchClause matchClause = new MatchClause(path);
|
||||
|
||||
if (matchClause.hasRelationship()) {
|
||||
if (!addedMatchClause && matchClause.hasRelationship()) {
|
||||
matchClauses.add(matchClause);
|
||||
}
|
||||
}
|
||||
@@ -106,8 +108,7 @@ public class CypherQuery implements CypherQueryDefinition {
|
||||
for (Sort.Order o : sorts) {
|
||||
entityAwareOrders.add( getEntityAwareOrderRef(o) );
|
||||
}
|
||||
Sort entityAwareSort = new Sort(entityAwareOrders);
|
||||
return entityAwareSort;
|
||||
return new Sort(entityAwareOrders);
|
||||
}
|
||||
|
||||
private Sort.Order getEntityAwareOrderRef(Sort.Order o) {
|
||||
|
||||
@@ -20,7 +20,6 @@ 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.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
|
||||
/**
|
||||
@@ -36,7 +35,7 @@ class CypherQueryBuilder {
|
||||
public CypherQueryBuilder(MappingContext<? extends Neo4jPersistentEntity<?>, Neo4jPersistentProperty> context, Class<?> type, Neo4jTemplate template) {
|
||||
this.context = context;
|
||||
Neo4jPersistentEntity<?> entity = context.getPersistentEntity(type);
|
||||
this.query = new CypherQuery(entity, template, template.getInfrastructure().getNodeTypeRepresentationStrategy());
|
||||
this.query = new CypherQuery(entity, template, template.isLabelBased());
|
||||
}
|
||||
|
||||
public CypherQueryBuilder asCountQuery() {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import static org.springframework.data.neo4j.repository.query.QueryTemplates.*;
|
||||
|
||||
/**
|
||||
* Representation of a Cypher {@literal where} clause specifically for
|
||||
* use to narrow the results based on particular entity types, where
|
||||
* those entities can be identified via specific Labels (as per the
|
||||
* Label Based Type Representation Strategy)
|
||||
*
|
||||
* @author Nicki Watt
|
||||
*/
|
||||
public class IdPropertyWhereClause extends WhereClause {
|
||||
|
||||
|
||||
|
||||
public IdPropertyWhereClause(PartInfo partInfo, Neo4jTemplate template) {
|
||||
super(partInfo, template);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String operator = SYMBOLS.get(type);
|
||||
String variable = partInfo.getIdentifier();
|
||||
String result = String.format(WHERE_CLAUSE_ID, variable, operator, partInfo.getParameterIndex());
|
||||
if (EnumSet.of(Part.Type.NOT_IN).contains(type)) {
|
||||
result = "not( "+result+" )";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object convertValue(PartInfo partInfo, Object value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,10 @@ class MatchClause {
|
||||
}
|
||||
|
||||
private String matchPattern(VariableContext variableContext, PersistentPropertyPath<Neo4jPersistentProperty> relPath) {
|
||||
if (!relPath.getLeafProperty().isRelationship()) {
|
||||
final Neo4jPersistentProperty property = relPath.getBaseProperty();
|
||||
return formatMatch(variableContext.getVariableFor(property.getOwner()));
|
||||
}
|
||||
if (relPath.getLength() == 1) {
|
||||
final Neo4jPersistentProperty property = relPath.getBaseProperty();
|
||||
return formatMatch(variableContext.getVariableFor(property.getOwner()),
|
||||
@@ -75,6 +79,9 @@ class MatchClause {
|
||||
variableContext.getVariableFor(relPath));
|
||||
}
|
||||
|
||||
private String formatMatch(String single) {
|
||||
return String.format(QueryTemplates.MATCH_CLAUSE_SINGLE, single);
|
||||
}
|
||||
private String formatMatch(String first, String arrow, String second) {
|
||||
return String.format(QueryTemplates.MATCH_CLAUSE, first, arrow, second);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ public abstract class QueryTemplates {
|
||||
private static final String DIRECTION_INCOMING = "<-[:`%s`]-";
|
||||
private static final String DIRECTION_OUTGOING = "-[:`%s`]->";
|
||||
private static final String DIRECTION_BOTH = "-[:`%s`]-";
|
||||
static final String MATCH_CLAUSE_SINGLE = "(`%s`)";
|
||||
static final String MATCH_CLAUSE = "(`%s`)%s(`%s`)";
|
||||
static final String MATCH_CLAUSE2 = "%s%s(`%s`)";
|
||||
|
||||
@@ -53,6 +54,7 @@ public abstract class QueryTemplates {
|
||||
static final String START_CLAUSE_INDEX_LOOKUP = "`%s`=node:`%s`(`%s`=" + PLACEHOLDER + ")";
|
||||
static final String START_CLAUSE_INDEX_QUERY = "`%s`=node:`%s`(" + PLACEHOLDER + ")";
|
||||
static final String WHERE_CLAUSE_1 = "`%1$s`.`%2$s` %3$s {%4$d}";
|
||||
static final String WHERE_CLAUSE_ID = "id(`%1$s`) %2$s {%3$d}";
|
||||
static final String INDEXBASED_WHERE_TYPE_CHECK = "`%1$s`.__type__ IN [%2$s]";
|
||||
static final String LABELBASED_WHERE_TYPE_CHECK = "`%1$s`:%2$s";
|
||||
static final String WHERE_CLAUSE_0 = "`%1$s`.`%2$s` %3$s ";
|
||||
|
||||
@@ -67,7 +67,7 @@ class WhereClause {
|
||||
}
|
||||
|
||||
protected final PartInfo partInfo;
|
||||
private final Type type;
|
||||
protected final Type type;
|
||||
private PropertyConverter propertyConverter;
|
||||
|
||||
public WhereClause(PartInfo partInfo, Neo4jTemplate template) {
|
||||
@@ -108,15 +108,18 @@ class WhereClause {
|
||||
public Map<Parameter, Object> resolveParameters(Map<Parameter, Object> parameters) {
|
||||
for (Map.Entry<Parameter, Object> entry : parameters.entrySet()) {
|
||||
if (partInfo.getParameterIndex() == entry.getKey().getIndex()) {
|
||||
Object value = entry.getValue();
|
||||
if (EnumSet.of(Type.CONTAINING,Type.STARTING_WITH,Type.ENDING_WITH).contains(type))
|
||||
value = QueryTemplates.formatExpression(partInfo, value);
|
||||
else if (propertyConverter!=null) {
|
||||
value = propertyConverter.serializePropertyValue(value);
|
||||
}
|
||||
entry.setValue(value);
|
||||
entry.setValue(convertValue(partInfo, entry.getValue()));
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
protected Object convertValue(PartInfo partInfo, Object value) {
|
||||
if (EnumSet.of(Type.CONTAINING, Type.STARTING_WITH, Type.ENDING_WITH).contains(type))
|
||||
return QueryTemplates.formatExpression(this.partInfo, value);
|
||||
else if (propertyConverter!=null) {
|
||||
return propertyConverter.serializePropertyValue(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister;
|
||||
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.support.node.EntityStateFactory;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
|
||||
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
|
||||
@@ -71,4 +72,8 @@ public interface Infrastructure {
|
||||
TypeRepresentationStrategy<Relationship> getRelationshipTypeRepresentationStrategy();
|
||||
|
||||
TypeSafetyPolicy getTypeSafetyPolicy();
|
||||
|
||||
SchemaIndexProvider getSchemaIndexProvider();
|
||||
|
||||
CypherQueryExecutor getCypherQueryExecutor();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister;
|
||||
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
|
||||
import org.springframework.data.neo4j.support.node.EntityStateFactory;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
|
||||
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
|
||||
@@ -58,11 +59,12 @@ public class MappingInfrastructure implements Infrastructure {
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
private final ResultConverter resultConverter;
|
||||
private final IndexProvider indexProvider;
|
||||
private final SchemaIndexProvider schemaIndexProvider;
|
||||
private final GraphDatabaseService graphDatabaseService;
|
||||
private final GraphDatabase graphDatabase;
|
||||
private final TypeSafetyPolicy typeSafetyPolicy;
|
||||
|
||||
public MappingInfrastructure(GraphDatabase graphDatabase, GraphDatabaseService graphDatabaseService, IndexProvider indexProvider, ResultConverter resultConverter, PlatformTransactionManager transactionManager, TypeRepresentationStrategies typeRepresentationStrategies, EntityRemover entityRemover, Neo4jEntityPersister entityPersister, EntityStateHandler entityStateHandler, CypherQueryExecutor cypherQueryExecutor, Neo4jMappingContext mappingContext, TypeRepresentationStrategy<Relationship> relationshipTypeRepresentationStrategy, TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy, Validator validator, ConversionService conversionService, TypeSafetyPolicy typeSafetyPolicy) {
|
||||
public MappingInfrastructure(GraphDatabase graphDatabase, GraphDatabaseService graphDatabaseService, IndexProvider indexProvider, ResultConverter resultConverter, PlatformTransactionManager transactionManager, TypeRepresentationStrategies typeRepresentationStrategies, EntityRemover entityRemover, Neo4jEntityPersister entityPersister, EntityStateHandler entityStateHandler, CypherQueryExecutor cypherQueryExecutor, Neo4jMappingContext mappingContext, TypeRepresentationStrategy<Relationship> relationshipTypeRepresentationStrategy, TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy, Validator validator, ConversionService conversionService, SchemaIndexProvider schemaIndexProvider, TypeSafetyPolicy typeSafetyPolicy) {
|
||||
this.graphDatabase = graphDatabase;
|
||||
this.graphDatabaseService = graphDatabaseService;
|
||||
this.indexProvider = indexProvider;
|
||||
@@ -78,6 +80,7 @@ public class MappingInfrastructure implements Infrastructure {
|
||||
this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy;
|
||||
this.validator = validator;
|
||||
this.conversionService = conversionService;
|
||||
this.schemaIndexProvider = schemaIndexProvider;
|
||||
this.typeSafetyPolicy = typeSafetyPolicy;
|
||||
}
|
||||
|
||||
@@ -155,4 +158,14 @@ public class MappingInfrastructure implements Infrastructure {
|
||||
public TypeSafetyPolicy getTypeSafetyPolicy() {
|
||||
return typeSafetyPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SchemaIndexProvider getSchemaIndexProvider() {
|
||||
return schemaIndexProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CypherQueryExecutor getCypherQueryExecutor() {
|
||||
return cypherQueryExecutor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.springframework.data.neo4j.support.node.NodeEntityStateFactory;
|
||||
import org.springframework.data.neo4j.support.query.CypherQueryExecutor;
|
||||
import org.springframework.data.neo4j.support.relationship.RelationshipEntityInstantiator;
|
||||
import org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory;
|
||||
import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategies;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory;
|
||||
import org.springframework.data.neo4j.support.typesafety.TypeSafetyPolicy;
|
||||
@@ -77,6 +78,7 @@ public class MappingInfrastructureFactoryBean implements FactoryBean<Infrastruct
|
||||
private PlatformTransactionManager transactionManager;
|
||||
private ResultConverter resultConverter;
|
||||
private IndexProvider indexProvider;
|
||||
private SchemaIndexProvider schemaIndexProvider;
|
||||
private GraphDatabaseService graphDatabaseService;
|
||||
private GraphDatabase graphDatabase;
|
||||
private IsNewStrategyFactory isNewStrategyFactory;
|
||||
@@ -155,13 +157,16 @@ public class MappingInfrastructureFactoryBean implements FactoryBean<Infrastruct
|
||||
}
|
||||
this.graphDatabase.setResultConverter(resultConverter);
|
||||
this.cypherQueryExecutor = new CypherQueryExecutor(graphDatabase.queryEngineFor(QueryType.Cypher, resultConverter));
|
||||
if (schemaIndexProvider == null) {
|
||||
schemaIndexProvider = new SchemaIndexProvider(graphDatabase);
|
||||
}
|
||||
if (this.indexProvider == null) {
|
||||
this.indexProvider = new IndexProviderImpl(graphDatabase);
|
||||
}
|
||||
if (this.typeSafetyPolicy == null) {
|
||||
this.typeSafetyPolicy = new TypeSafetyPolicy();
|
||||
}
|
||||
this.mappingInfrastructure = new MappingInfrastructure(graphDatabase, graphDatabaseService, indexProvider, resultConverter, transactionManager, typeRepresentationStrategies, entityRemover, entityPersister, entityStateHandler, cypherQueryExecutor, mappingContext, relationshipTypeRepresentationStrategy, nodeTypeRepresentationStrategy, validator, conversionService, typeSafetyPolicy);
|
||||
this.mappingInfrastructure = new MappingInfrastructure(graphDatabase, graphDatabaseService, indexProvider, resultConverter, transactionManager, typeRepresentationStrategies, entityRemover, entityPersister, entityStateHandler, cypherQueryExecutor, mappingContext, relationshipTypeRepresentationStrategy, nodeTypeRepresentationStrategy, validator, conversionService, schemaIndexProvider, typeSafetyPolicy);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("error initializing "+getClass().getName(),e);
|
||||
}
|
||||
|
||||
@@ -295,6 +295,10 @@ public class Neo4jTemplate implements Neo4jOperations, ApplicationContextAware {
|
||||
return getMappingContext().isRelationshipEntity(targetType);
|
||||
}
|
||||
|
||||
public boolean isLabelBased() {
|
||||
return getInfrastructure().getNodeTypeRepresentationStrategy().isLabelBased();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T save(T entity) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.data.neo4j.annotation.Indexed;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
|
||||
|
||||
import static org.springframework.data.neo4j.support.ParameterCheck.notNull;
|
||||
|
||||
|
||||
@@ -23,11 +23,15 @@ import java.util.Map;
|
||||
|
||||
public enum IndexType
|
||||
{
|
||||
SIMPLE{ public Map<String,String> getConfig() { return LuceneIndexImplementation.EXACT_CONFIG; } },
|
||||
SIMPLE { public Map<String,String> getConfig() { return LuceneIndexImplementation.EXACT_CONFIG; } },
|
||||
LABEL { public Map<String,String> getConfig() { return null; } public boolean isLabelBased() { return true; }},
|
||||
FULLTEXT { public Map<String,String> getConfig() { return LuceneIndexImplementation.FULLTEXT_CONFIG; } },
|
||||
POINT { public Map<String,String> getConfig() { return MapUtil.stringMap(
|
||||
IndexManager.PROVIDER, "spatial", "geometry_type" , "point","wkt","wkt") ; } },
|
||||
UNIQUE(){ public Map<String, String> getConfig() { return LuceneIndexImplementation.EXACT_CONFIG; } };
|
||||
POINT { public Map<String,String> getConfig() { return MapUtil.stringMap(
|
||||
IndexManager.PROVIDER, "spatial", "geometry_type" , "point","wkt","wkt") ; } }
|
||||
|
||||
;
|
||||
|
||||
public abstract Map<String, String>getConfig();
|
||||
|
||||
public boolean isLabelBased() { return false; }
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.neo4j.support.mapping;
|
||||
|
||||
import org.neo4j.graphdb.index.Index;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.context.MappingContextEvent;
|
||||
@@ -23,6 +22,7 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.index.IndexProvider;
|
||||
import org.springframework.data.neo4j.support.index.IndexType;
|
||||
import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
@@ -30,8 +30,11 @@ import org.springframework.data.neo4j.support.index.IndexType;
|
||||
*/
|
||||
public class IndexCreationMappingEventListener implements ApplicationListener<MappingContextEvent<Neo4jPersistentEntity<?>, Neo4jPersistentProperty>> {
|
||||
private IndexProvider indexProvider;
|
||||
public IndexCreationMappingEventListener(IndexProvider indexProvider) {
|
||||
private SchemaIndexProvider schemaIndexProvider;
|
||||
|
||||
public IndexCreationMappingEventListener(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider) {
|
||||
this.indexProvider = indexProvider;
|
||||
this.schemaIndexProvider = schemaIndexProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,11 +46,14 @@ public class IndexCreationMappingEventListener implements ApplicationListener<Ma
|
||||
|
||||
private void ensureEntityIndexes(Neo4jPersistentEntity<?> entity) {
|
||||
final Class entityType = entity.getType();
|
||||
indexProvider.getIndex(entity, null, IndexType.SIMPLE);
|
||||
indexProvider.getIndex(entity, null, IndexType.SIMPLE); // TODO only when TRS is non-label?
|
||||
entity.doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
|
||||
@Override
|
||||
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
|
||||
if (property.isIndexed()) {
|
||||
if (!property.isIndexed()) return;
|
||||
if (property.getIndexInfo().isLabelBased()) {
|
||||
schemaIndexProvider.createIndex(property);
|
||||
} else {
|
||||
indexProvider.getIndex(property, entityType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,7 @@ import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.neo4j.annotation.NodeEntity;
|
||||
import org.springframework.data.neo4j.annotation.RelationshipEntity;
|
||||
import org.springframework.data.neo4j.mapping.ManagedEntity;
|
||||
import org.springframework.data.neo4j.mapping.MappingPolicy;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.mapping.RelationshipProperties;
|
||||
import org.springframework.data.neo4j.mapping.*;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
@@ -75,10 +71,10 @@ public class Neo4jPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4j
|
||||
super.verify();
|
||||
doWithProperties(new PropertyHandler<Neo4jPersistentProperty>() {
|
||||
Neo4jPersistentProperty unique = null;
|
||||
public void doWithPersistentProperty(Neo4jPersistentProperty persistentProperty) {
|
||||
if (persistentProperty.isUnique()) {
|
||||
if (unique!=null) throw new MappingException("Duplicate unique property " + persistentProperty.getName()+ ", " + unique.getName() + " has already been defined. Only one unique property is allowed per type");
|
||||
unique = persistentProperty;
|
||||
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
|
||||
if (property.isUnique()) {
|
||||
if (unique!=null) throw new MappingException("Duplicate unique property " + qualifiedPropertyName(property)+ ", " + qualifiedPropertyName(uniqueProperty) + " has already been defined. Only one unique property is allowed per type");
|
||||
unique = property;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -87,7 +83,11 @@ public class Neo4jPersistentEntityImpl<T> extends BasicPersistentEntity<T, Neo4j
|
||||
}
|
||||
final Neo4jPersistentProperty idProperty = getIdProperty();
|
||||
if (idProperty == null) throw new MappingException("No id property in " + this);
|
||||
if (idProperty.getType().isPrimitive()) throw new MappingException("The type of the id-property in " + idProperty+" must not be a primitive type but an object type like java.lang.Long");
|
||||
if (idProperty.getType().isPrimitive()) throw new MappingException("The type of the id-property in " + qualifiedPropertyName(idProperty)+" must not be a primitive type but an object type like java.lang.Long");
|
||||
}
|
||||
|
||||
private String qualifiedPropertyName(Neo4jPersistentProperty persistentProperty) {
|
||||
return getName() + "." + persistentProperty.getName();
|
||||
}
|
||||
|
||||
public boolean useShortNames() {
|
||||
|
||||
@@ -113,4 +113,13 @@ public class StoredEntityType {
|
||||
public String toString() {
|
||||
return String.format("StoredEntityType for %s with alias %s",getType(),getAlias());
|
||||
}
|
||||
|
||||
public StoredEntityType findByTypeClass(Class type) {
|
||||
if (getType().equals(type)) return this;
|
||||
for (StoredEntityType superType : superTypes) {
|
||||
StoredEntityType foundType = superType.findByTypeClass(type);
|
||||
if (foundType!=null) return foundType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.springframework.data.neo4j.support.schema;
|
||||
|
||||
import org.springframework.data.neo4j.annotation.QueryType;
|
||||
import org.springframework.data.neo4j.conversion.EndResult;
|
||||
import org.springframework.data.neo4j.conversion.Result;
|
||||
import org.springframework.data.neo4j.core.GraphDatabase;
|
||||
import org.springframework.data.neo4j.mapping.IndexInfo;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity;
|
||||
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
|
||||
import org.springframework.data.neo4j.support.query.QueryEngine;
|
||||
|
||||
import static org.neo4j.helpers.collection.MapUtil.map;
|
||||
|
||||
/**
|
||||
* @author mh
|
||||
* @since 20.01.14
|
||||
*/
|
||||
public class SchemaIndexProvider {
|
||||
private final GraphDatabase gd;
|
||||
private final QueryEngine<Object> cypher;
|
||||
|
||||
|
||||
public SchemaIndexProvider(GraphDatabase gd) {
|
||||
this.gd = gd;
|
||||
cypher = gd.queryEngineFor(QueryType.Cypher);
|
||||
|
||||
}
|
||||
|
||||
public void createIndex(Neo4jPersistentProperty property) {
|
||||
IndexInfo indexInfo = property.getIndexInfo();
|
||||
String label = indexInfo.getIndexName();
|
||||
String prop = property.getNeo4jPropertyName();
|
||||
String query = indexQuery(label, prop, indexInfo.isUnique());
|
||||
Result<Object> result = cypher.query(query, null);
|
||||
}
|
||||
|
||||
public <T> EndResult<T> findAll(Neo4jPersistentEntity entity) {
|
||||
String label = entity.getTypeAlias().toString();
|
||||
String query = findByLabelQuery(label);
|
||||
return cypher.query(query, null).<T>to(entity.getType());
|
||||
}
|
||||
|
||||
public <T> EndResult<T> findAll(Neo4jPersistentProperty property, Object value) {
|
||||
IndexInfo indexInfo = property.getIndexInfo();
|
||||
String label = indexInfo.getIndexName();
|
||||
String prop = property.getNeo4jPropertyName();
|
||||
String query = findByLabelAndPropertyQuery(label, prop);
|
||||
return cypher.query(query, map("value", value)).<T>to((Class<T>)property.getOwner().getType());
|
||||
}
|
||||
|
||||
private String findByLabelQuery(String label) {
|
||||
return "MATCH (n:`"+label+"`) RETURN n";
|
||||
}
|
||||
|
||||
private String findByLabelAndPropertyQuery(String label, String prop) {
|
||||
return "MATCH (n:`"+label+"` {`"+prop+"`:{value}}) RETURN n";
|
||||
}
|
||||
|
||||
private String indexQuery(String label, String prop, boolean unique) {
|
||||
if (unique) {
|
||||
return "CREATE CONSTRAINT ON (n:`"+ label +"`) ASSERT n.`"+ prop +"` IS UNIQUE";
|
||||
}
|
||||
return "CREATE INDEX ON :`"+ label +"`(`"+ prop +"`)";
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,11 @@ public abstract class AbstractIndexBasedTypeRepresentationStrategy<S extends Pro
|
||||
remove(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLabelBased() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void remove(S state) {
|
||||
try {
|
||||
typesIndex.remove(state);
|
||||
|
||||
@@ -130,6 +130,11 @@ public class LabelBasedNodeTypeRepresentationStrategy implements NodeTypeReprese
|
||||
public void preEntityRemoval(Node state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLabelBased() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isStrategyAlreadyInUse(GraphDatabase graphDatabaseService) {
|
||||
return graphDatabaseService.getAllLabelNames().contains(SDN_LABEL_STRATEGY);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,11 @@ public class NoopNodeTypeRepresentationStrategy implements NodeTypeRepresentatio
|
||||
public void preEntityRemoval(Node state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLabelBased() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> readAliasFrom(Node state) {
|
||||
return null;
|
||||
|
||||
@@ -41,6 +41,11 @@ public class NoopRelationshipTypeRepresentationStrategy implements RelationshipT
|
||||
public void preEntityRemoval(Relationship state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLabelBased() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readAliasFrom(Relationship state) {
|
||||
return null;
|
||||
|
||||
@@ -174,6 +174,11 @@ public class SubReferenceNodeTypeRepresentationStrategy implements NodeTypeRepre
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLabelBased() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> ClosableIterable<Node> findAll(final StoredEntityType type) {
|
||||
final Node subrefNode = findSubreferenceNode(type);
|
||||
|
||||
@@ -96,6 +96,11 @@ public class TypeRepresentationStrategies implements TypeRepresentationStrategy<
|
||||
getTypeRepresentationStrategy(state).preEntityRemoval(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLabelBased() {
|
||||
return nodeTypeRepresentationStrategy.isLabelBased();
|
||||
}
|
||||
|
||||
public TypeRepresentationStrategy<Node> getNodeTypeRepresentationStrategy() {
|
||||
return nodeTypeRepresentationStrategy;
|
||||
}
|
||||
|
||||
@@ -54,14 +54,14 @@ public abstract class AbstractCypherQueryBuilderTestBase {
|
||||
public void setUp() {
|
||||
Neo4jMappingContext context = new Neo4jMappingContext();
|
||||
Neo4jTemplate template = Mockito.mock(Neo4jTemplate.class);
|
||||
Infrastructure inf = Mockito.mock(Infrastructure.class);
|
||||
when (template.getInfrastructure()).thenReturn(inf);
|
||||
when (inf.getNodeTypeRepresentationStrategy()).thenReturn(getNodeTypeRepresentationStrategy());
|
||||
finishMock(template);
|
||||
this.query = new CypherQueryBuilder(context, Person.class, template);
|
||||
this.trsSpecificExpectedQuery = null;
|
||||
}
|
||||
|
||||
abstract NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy();
|
||||
protected void finishMock(Neo4jTemplate template) {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void createsQueryForSimplePropertyReference() {
|
||||
|
||||
@@ -42,6 +42,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Defines the tests for the various finder method based scenarios with
|
||||
@@ -84,6 +85,7 @@ public abstract class AbstractDerivedFinderMethodTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
protected final static String THING_NAME = Thing.class.getName();
|
||||
@Autowired
|
||||
ThingRepository repository;
|
||||
@Autowired
|
||||
@@ -391,7 +393,7 @@ public abstract class AbstractDerivedFinderMethodTestBase {
|
||||
* This method will either return the trs specific query string if
|
||||
* this was set, otherwise the default value passed in.
|
||||
*/
|
||||
private String getExpectedQuery(String defaultQueryString) {
|
||||
protected String getExpectedQuery(String defaultQueryString) {
|
||||
return (this.trsSpecificExpectedQuery != null)
|
||||
? this.trsSpecificExpectedQuery
|
||||
: defaultQueryString;
|
||||
@@ -404,7 +406,7 @@ public abstract class AbstractDerivedFinderMethodTestBase {
|
||||
* This method will either return the trs specific query params if
|
||||
* this was set, otherwise the default value passed in.
|
||||
*/
|
||||
private Object[] getExpectedParams(Object... defaultVals) {
|
||||
protected Object[] getExpectedParams(Object... defaultVals) {
|
||||
return (this.trsSpecificExpectedParams != null)
|
||||
? this.trsSpecificExpectedParams
|
||||
: (defaultVals == null) ? new Object[0] : defaultVals;
|
||||
@@ -418,7 +420,9 @@ public abstract class AbstractDerivedFinderMethodTestBase {
|
||||
String query = derivedCypherRepositoryQuery.createQueryWithPagingAndSorting(accessor);
|
||||
Map<String, Object> params = derivedCypherRepositoryQuery.resolveParams(accessor);
|
||||
String firstWord = expectedQuery.split("\\s+")[0];
|
||||
String actual = query.substring(query.indexOf(firstWord));
|
||||
int beginIndex = query.indexOf(firstWord);
|
||||
assertTrue("didn't find word "+firstWord+" in "+query,beginIndex != -1);
|
||||
String actual = query.substring(beginIndex);
|
||||
actual = actual.substring(0, Math.min(expectedQuery.length(),actual.length()));
|
||||
assertEquals(expectedQuery, actual);
|
||||
assertEquals(expectedParam.length,params.size());
|
||||
|
||||
@@ -39,10 +39,6 @@ public class CypherQueryBuilderForIndexBasedTRSUnitTests extends AbstractCypherQ
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
protected NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() {
|
||||
return Mockito.mock(IndexBasedNodeTypeRepresentationStrategy.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void createsQueryForLikeProperty() {
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
|
||||
import org.springframework.data.neo4j.support.Neo4jTemplate;
|
||||
import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
@@ -39,8 +40,9 @@ public class CypherQueryBuilderForLabelBasedTRSUnitTests extends AbstractCypherQ
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
protected NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() {
|
||||
return Mockito.mock(LabelBasedNodeTypeRepresentationStrategy.class);
|
||||
@Override
|
||||
protected void finishMock(Neo4jTemplate template) {
|
||||
Mockito.when(template.isLabelBased()).thenReturn(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,7 +103,7 @@ public class CypherQueryBuilderForLabelBasedTRSUnitTests extends AbstractCypherQ
|
||||
@Override
|
||||
@Test
|
||||
public void createsSimpleTraversalClauseCorrectly() {
|
||||
this.trsSpecificExpectedQuery = "START `person_group`=node({0}) MATCH (`person`)<-[:`members`]-(`person_group`) WHERE `person`:`Person` RETURN `person`";
|
||||
this.trsSpecificExpectedQuery = " MATCH (`person`)<-[:`members`]-(`person_group`) WHERE id(`person_group`) = {0} AND `person`:`Person` RETURN `person`";
|
||||
super.createsSimpleTraversalClauseCorrectly();
|
||||
}
|
||||
|
||||
@@ -120,14 +122,14 @@ public class CypherQueryBuilderForLabelBasedTRSUnitTests extends AbstractCypherQ
|
||||
@Override
|
||||
@Test
|
||||
public void shouldFindByNodeEntity() throws Exception {
|
||||
this.trsSpecificExpectedQuery = "START `person_pet`=node({0}) MATCH (`person`)-[:`owns`]->(`person_pet`) WHERE `person`:`Person` RETURN `person`";
|
||||
this.trsSpecificExpectedQuery = " MATCH (`person`)-[:`owns`]->(`person_pet`) WHERE id(`person_pet`) = {0} AND `person`:`Person` RETURN `person`";
|
||||
super.shouldFindByNodeEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Test
|
||||
public void shouldFindByNodeEntityForIncomingRelationship() {
|
||||
this.trsSpecificExpectedQuery = "START `person_group`=node({0}) MATCH (`person`)<-[:`members`]-(`person_group`) WHERE `person`:`Person` RETURN `person`";
|
||||
this.trsSpecificExpectedQuery = " MATCH (`person`)<-[:`members`]-(`person_group`) WHERE id(`person_group`) = {0} AND `person`:`Person` RETURN `person`";
|
||||
super.shouldFindByNodeEntityForIncomingRelationship();
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ import static org.hamcrest.Matchers.instanceOf;
|
||||
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
|
||||
public class DerivedFinderMethodForLabelBasedTRSTests extends AbstractDerivedFinderMethodTestBase {
|
||||
|
||||
private static final String DEFAULT_MATCH_CLAUSE = "MATCH (`thing`:`org.springframework.data.neo4j.repository.query.AbstractDerivedFinderMethodTestBase$Thing`)";
|
||||
private static final String DEFAULT_MATCH_CLAUSE = "MATCH (`thing`:`"+THING_NAME+"`)";
|
||||
|
||||
@Autowired
|
||||
NodeTypeRepresentationStrategy strategy;
|
||||
@@ -61,10 +61,19 @@ public class DerivedFinderMethodForLabelBasedTRSTests extends AbstractDerivedFin
|
||||
@Override
|
||||
public void testQueryWithEntityGraphId() throws Exception {
|
||||
// findByOwnerId
|
||||
this.trsSpecificExpectedQuery = "START `thing_owner`=node({0}) MATCH (`thing`)-[:`owner`]->(`thing_owner`) WHERE `thing`:`org.springframework.data.neo4j.repository.query.AbstractDerivedFinderMethodTestBase$Thing` ";
|
||||
this.trsSpecificExpectedQuery = "MATCH (`thing`)-[:`owner`]->(`thing_owner`) WHERE id(`thing_owner`) = {0} AND `thing`:`"+THING_NAME+"` RETURN `thing`";
|
||||
super.testQueryWithEntityGraphId();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryWithGraphId() throws Exception {
|
||||
assertRepositoryQueryMethod(ThingRepository.class,
|
||||
"findById",
|
||||
new Object[]{123},
|
||||
getExpectedQuery("MATCH (`thing`) WHERE id(`thing`) = {0}"),
|
||||
getExpectedParams(123));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Override
|
||||
public void testIndexQueryWithTwoParams() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user