From 8ebcabdfb2d262286b0f958aa34920f9e783de74 Mon Sep 17 00:00:00 2001 From: Nicki Watt Date: Sun, 9 Feb 2014 23:38:55 +0000 Subject: [PATCH] DATAGRAPH-388 : First stab at getting Schema Indexes (Label based) in --- .../LabelBasedIndexedPropertyEntityTests.java | 343 ++++++++++++++++++ ...angingAvertedViaInitialEntitySetTests.java | 68 ++++ ...asedIndexedPropertyHangingEntityTests.java | 70 ++++ ...odeTypeRepresentationStrategyTestBase.java | 4 +- ...rtyEntityTests-context-with-initialset.xml | 134 +++++++ ...asedIndexedPropertyEntityTests-context.xml | 134 +++++++ .../Neo4jGraphPersistenceTests-context.xml | 1 + .../data/neo4j/config/Neo4jConfiguration.java | 7 +- ...gPropertyFieldAccessorListenerFactory.java | 2 +- .../NodeDelegatingFieldAccessorFactory.java | 4 + ...gPropertyFieldAccessorListenerFactory.java | 79 ++++ .../data/neo4j/mapping/IndexInfo.java | 13 +- .../neo4j/repository/query/CypherQuery.java | 5 +- .../data/neo4j/repository/query/PartInfo.java | 3 + .../data/neo4j/support/Infrastructure.java | 3 - .../neo4j/support/MappingInfrastructure.java | 3 - .../MappingInfrastructureFactoryBean.java | 5 +- .../support/index/IndexProviderImpl.java | 1 - .../IndexCreationMappingEventListener.java | 26 +- .../support/mapping/Neo4jMappingContext.java | 50 +++ .../data/neo4j/support/node/Neo4jHelper.java | 3 + .../support/schema/SchemaIndexProvider.java | 51 ++- ...tIndexBasedTypeRepresentationStrategy.java | 1 - .../TypeRepresentationStrategyFactory.java | 4 - .../data/neo4j/model/Person.java | 11 + .../data/neo4j/repository/TestTeam.java | 3 + .../AbstractDerivedFinderMethodTestBase.java | 12 + ...edFinderMethodForIndexedBasedTRSTests.java | 32 ++ ...ivedFinderMethodForLabelBasedTRSTests.java | 12 + .../repository/query/ThingRepository.java | 3 + 30 files changed, 1061 insertions(+), 26 deletions(-) create mode 100644 spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests.java create mode 100644 spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingAvertedViaInitialEntitySetTests.java create mode 100644 spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingEntityTests.java create mode 100644 spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context-with-initialset.xml create mode 100644 spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context.xml create mode 100644 spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/SchemaIndexingPropertyFieldAccessorListenerFactory.java diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests.java new file mode 100644 index 000000000..45afb0436 --- /dev/null +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests.java @@ -0,0 +1,343 @@ +/** + * Copyright 2014 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.aspects.support; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.graphdb.DynamicLabel; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.schema.IndexDefinition; +import org.neo4j.helpers.collection.IteratorUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.neo4j.annotation.Indexed; +import org.springframework.data.neo4j.annotation.NodeEntity; +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.NodeTypeRepresentationStrategy; +import org.springframework.data.neo4j.support.Neo4jTemplate; +import org.springframework.data.neo4j.support.index.IndexType; +import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext; +import org.springframework.data.neo4j.support.mapping.StoredEntityType; +import org.springframework.data.neo4j.support.node.Neo4jHelper; +import org.springframework.data.neo4j.support.query.QueryEngine; +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.TransactionalTestExecutionListener; + +import java.util.*; + +import static java.lang.String.format; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.*; + +/** + * @author Nicki Watt + * @since 09-02-2014 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { + "classpath:org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context.xml", + "classpath:org/springframework/data/neo4j/aspects/support/LabelingTypeRepresentationStrategyOverride-context.xml"}) +@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class}) +public class LabelBasedIndexedPropertyEntityTests { + + @Autowired + protected NodeTypeRepresentationStrategy nodeTypeRepresentationStrategy; + + @Autowired + protected GraphDatabaseService graphDatabaseService; + @Autowired + protected Neo4jTemplate neo4jTemplate; + @Autowired + protected Neo4jMappingContext ctx; + + protected QueryEngine queryEngine; + + protected Thing thing; + protected SubThing subThing; + protected SubSubThing subSubThing; + protected StoredEntityType thingType; + protected StoredEntityType subThingType; + protected StoredEntityType subSubThingType; + + @Before + public void cleanDb() { + assertTrue("This test expects a Label Based TRS to be in place and it is not!",neo4jTemplate.isLabelBased()); + try (Transaction tx = graphDatabaseService.beginTx()) { + Neo4jHelper.cleanDb(neo4jTemplate); + tx.success(); + } + queryEngine = neo4jTemplate.queryEngineFor(QueryType.Cypher); + + // Hangs if these are done in the same TX see + // LabelBasedIndexedPropertyHangingEntityTests + try (Transaction tx = graphDatabaseService.beginTx()) { + createThing(); + tx.success(); + } + try (Transaction tx = graphDatabaseService.beginTx()) { + createSubThing(); + tx.success(); + } + try (Transaction tx = graphDatabaseService.beginTx()) { + createSubSubThing(); + tx.success(); + } + } + + @Test + public void testSchemaBasedPropertyIndexesExistPostCreationForBaseEntity() { + assertOnlyValidPropertyIndexedLabelsExist( + (String) thingType.getAlias(), + "schemaIndexedCommonName", + "schemaIndexedThingName"); + } + + @Test + public void testSchemaBasedPropertyIndexesExistPostCreationForHierarchicalEntities() { + assertOnlyValidPropertyIndexedLabelsExist( + (String) thingType.getAlias(), + "schemaIndexedCommonName", + "schemaIndexedThingName"); + + assertOnlyValidPropertyIndexedLabelsExist( + (String) subThingType.getAlias(), + "schemaIndexedCommonName", + "schemaIndexedThingName", + "schemaIndexedSubThingName"); + + assertOnlyValidPropertyIndexedLabelsExist( + (String) subSubThingType.getAlias(), + "schemaIndexedCommonName", + "schemaIndexedThingName", + "schemaIndexedSubThingName", + "schemaIndexedSubSubThingName"); + } + + @Test + public void testAbleToQueryUsingSchemaIndex() { + Collection names = executeQuery("Thing","schemaIndexedThingName","thing-theSchemaIndexedThingName"); + assertThat( names , containsInAnyOrder( "thing" )); + } + + @Test + public void testAbleToQueryUsingSchemaIndexAgainstHierarchy() { + Collection names1 = executeQuery( "Thing", "schemaIndexedThingName","thing-theSchemaIndexedThingName"); + assertThat( names1 , containsInAnyOrder( "thing" )); + + Collection names2 = executeQuery( "Thing", "schemaIndexedThingName","subThing-theSchemaIndexedThingName"); + assertThat( names2 , containsInAnyOrder( "subThing" )); + + Collection names3 = executeQuery( "Thing", "schemaIndexedThingName","subSubThing-theSchemaIndexedThingName"); + assertThat( names3 , containsInAnyOrder( "subSubThing" )); + } + + @Test + public void testAbleToQueryAndFindCommonValuesUsingSchemaIndexAcrossHierarchy() { + Collection names1 = executeQuery( "Thing", "schemaIndexedCommonName","common"); + assertThat( names1 , containsInAnyOrder( "thing" ,"subThing" ,"subSubThing" )); + + } + + private Collection executeQuery(String label, String indexedPropName, String indexedPropValue) { + Map params = new HashMap(); + params.put("indexedPropValue",indexedPropValue); + Result result = queryEngine.query( + "MATCH (n:"+label+") " + + "USING INDEX n:Thing(" + indexedPropName + ") " + + "where n." +indexedPropName + " = {indexedPropValue}" + + "return n.name", params); + assertNotNull(result); + EndResult results = result.to(String.class); + return IteratorUtil.asCollection(results.iterator()); + } + + /** + * For a given Label, this method will ensure that the only label index + * definitions associated with it, are those passed in to the method, + * throwing an Assertion error if this is not the case + * + * @param label + * @param propertyNames + */ + private void assertOnlyValidPropertyIndexedLabelsExist(String label, String... propertyNames) { + try (Transaction tx = graphDatabaseService.beginTx()) { + Iterable idefs = graphDatabaseService.schema().getIndexes(DynamicLabel.label(label)); + Set propNames2Find = new HashSet(); + Set extraPropNames = new HashSet(); + propNames2Find.addAll( Arrays.asList(propertyNames)); + + for (IndexDefinition idef: idefs) { + assertEquals( idef.getLabel().name() , label ); + for (String key : idef.getPropertyKeys()) { + if (propNames2Find.contains(key)) + propNames2Find.remove(key); + else + extraPropNames.add(key); + } + } + + // We remove all the property names we find so by this point, + // all we are looking for should be removed + assertThat( format("Not all properties expected to be created as schema labels found missing (%s)",propNames2Find), propNames2Find, hasSize(0)); + assertThat( format("Additional properties created as schema labels (%s) not expected",extraPropNames) , extraPropNames, hasSize(0)); + } + } + + + protected void createThing() { + thing = new Thing(); + thing.setName("thing"); + thing.setLegacyIndexedThingName("thing-theLegacyIndexedThingName"); + thing.setSchemaIndexedThingName("thing-theSchemaIndexedThingName"); + thing.setSchemaIndexedCommonName("common"); + neo4jTemplate.save(thing); + + thingType = neo4jTemplate.getStoredEntityType(thing); + } + + protected void createSubThing() { + subThing = new SubThing(); + subThing.setName("subThing"); + subThing.setLegacyIndexedThingName("subThing-theLegacyIndexedThingName"); + subThing.setSchemaIndexedThingName("subThing-theSchemaIndexedThingName"); + subThing.setLegacyIndexedSubThingName("subThing-theLegacyIndexedSubThingName"); + subThing.setSchemaIndexedSubThingName("subThing-theSchemaIndexedSubThingName"); + subThing.setSchemaIndexedCommonName("common"); + neo4jTemplate.save(subThing); + + subThingType = neo4jTemplate.getStoredEntityType(subThing); + } + + protected void createSubSubThing() { + subSubThing = new SubSubThing(); + subSubThing.setName("subSubThing"); + subSubThing.setLegacyIndexedThingName("subSubThing-theLegacyIndexedThingName"); + subSubThing.setSchemaIndexedThingName("subSubThing-theSchemaIndexedThingName"); + subSubThing.setLegacyIndexedSubThingName("subSubThing-theLegacyIndexedSubThingName"); + subSubThing.setSchemaIndexedSubThingName("subSubThing-theSchemaIndexedSubThingName"); + subSubThing.setLegacyIndexedSubSubThingName("subSubThing-theLegacyIndexedSubSubThingName"); + subSubThing.setSchemaIndexedSubSubThingName("subSubThing-theSchemaIndexedSubSubThingName"); + subSubThing.setSchemaIndexedCommonName("common"); + neo4jTemplate.save(subSubThing); + + subSubThingType = neo4jTemplate.getStoredEntityType(subSubThing); + } + + + @NodeEntity + public static class Thing { + + String name; + + @Indexed(indexType = IndexType.SIMPLE) + String legacyIndexedThingName; + + @Indexed(indexType = IndexType.LABEL, numeric = false) + String schemaIndexedCommonName; + + @Indexed(indexType = IndexType.LABEL, numeric = false) + String schemaIndexedThingName; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public String getLegacyIndexedThingName() { + return legacyIndexedThingName; + } + + public void setLegacyIndexedThingName(String legacyIndexedThingName) { + this.legacyIndexedThingName = legacyIndexedThingName; + } + + public String getSchemaIndexedThingName() { + return schemaIndexedThingName; + } + + public void setSchemaIndexedThingName(String schemaIndexedThingName) { + this.schemaIndexedThingName = schemaIndexedThingName; + } + + public String getSchemaIndexedCommonName() { + return schemaIndexedCommonName; + } + + public void setSchemaIndexedCommonName(String schemaIndexedCommonName) { + this.schemaIndexedCommonName = schemaIndexedCommonName; + } + } + + public static class SubThing extends Thing { + @Indexed(indexType = IndexType.SIMPLE) + String legacyIndexedSubThingName; + + @Indexed(indexType = IndexType.LABEL, numeric = false) + String schemaIndexedSubThingName; + + public String getLegacyIndexedSubThingName() { + return legacyIndexedSubThingName; + } + + public void setLegacyIndexedSubThingName(String legacyIndexedSubThingName) { + this.legacyIndexedSubThingName = legacyIndexedSubThingName; + } + + public String getSchemaIndexedSubThingName() { + return schemaIndexedSubThingName; + } + + public void setSchemaIndexedSubThingName(String schemaIndexedSubThingName) { + this.schemaIndexedSubThingName = schemaIndexedSubThingName; + } + } + + public static class SubSubThing extends SubThing { + @Indexed(indexType = IndexType.SIMPLE) + String legacyIndexedSubSubThingName; + + @Indexed(indexType = IndexType.LABEL, numeric = false) + String schemaIndexedSubSubThingName; + + public String getLegacyIndexedSubSubThingName() { + return legacyIndexedSubSubThingName; + } + + public void setLegacyIndexedSubSubThingName(String legacyIndexedSubSubThingName) { + this.legacyIndexedSubSubThingName = legacyIndexedSubSubThingName; + } + + public String getSchemaIndexedSubSubThingName() { + return schemaIndexedSubSubThingName; + } + + public void setSchemaIndexedSubSubThingName(String schemaIndexedSubSubThingName) { + this.schemaIndexedSubSubThingName = schemaIndexedSubSubThingName; + } + } +} diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingAvertedViaInitialEntitySetTests.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingAvertedViaInitialEntitySetTests.java new file mode 100644 index 000000000..052703ac8 --- /dev/null +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingAvertedViaInitialEntitySetTests.java @@ -0,0 +1,68 @@ +/** + * Copyright 2014 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.aspects.support; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.neo4j.graphdb.Transaction; +import org.springframework.data.neo4j.annotation.QueryType; +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 static org.junit.Assert.assertTrue; + +/** + * This version DOES work, only because the entities have been setup as part of the + * initialSet for the mappingContext. Compare with the other LabelBasedIndex...Tests + * + * @author Nicki Watt + * @since 09-02-2014 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { + "classpath:org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context-with-initialset.xml", + "classpath:org/springframework/data/neo4j/aspects/support/LabelingTypeRepresentationStrategyOverride-context.xml"}) +@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class}) +public class LabelBasedIndexedPropertyHangingAvertedViaInitialEntitySetTests extends LabelBasedIndexedPropertyEntityTests{ + + @Override + @Before + public void cleanDb() { + assertTrue("This test expects a Label Based TRS to be in place and it is not!",neo4jTemplate.isLabelBased()); + try (Transaction tx = graphDatabaseService.beginTx()) { + Neo4jHelper.cleanDb(neo4jTemplate); + tx.success(); + } + queryEngine = neo4jTemplate.queryEngineFor(QueryType.Cypher); + + // NW-ISSUE01 + // This now works because of the initialEntitySet in mappingContext + // see LabelBasedIndexedPropertyEntityTests-context-with-initialset.xml + try (Transaction tx = graphDatabaseService.beginTx()) { + createThing(); + createSubThing(); + createSubSubThing(); + tx.success(); + } + } + +} diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingEntityTests.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingEntityTests.java new file mode 100644 index 000000000..475171955 --- /dev/null +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyHangingEntityTests.java @@ -0,0 +1,70 @@ +/** + * Copyright 2014 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.aspects.support; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.runner.RunWith; +import org.neo4j.graphdb.Transaction; +import org.springframework.data.neo4j.annotation.QueryType; +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 static org.junit.Assert.assertTrue; + +/** + * This test actually does not work, it hangs, however is here to demonstrate + * some of the issues involved. Compare with the other LabelBasedIndex...Tests + * + * @author Nicki Watt + * @since 09-02-2014 + */ +@Ignore("NW-ISSUE01 - This version of the test causes deadlock situation when trying to create indexes") +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { + "classpath:org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context.xml", + "classpath:org/springframework/data/neo4j/aspects/support/LabelingTypeRepresentationStrategyOverride-context.xml"}) +@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class}) +public class LabelBasedIndexedPropertyHangingEntityTests extends LabelBasedIndexedPropertyEntityTests{ + + @Override + @Before + public void cleanDb() { + assertTrue("This test expects a Label Based TRS to be in place and it is not!",neo4jTemplate.isLabelBased()); + try (Transaction tx = graphDatabaseService.beginTx()) { + Neo4jHelper.cleanDb(neo4jTemplate); + tx.success(); + } + queryEngine = neo4jTemplate.queryEngineFor(QueryType.Cypher); + + // NW-ISSUE01 + // By doing this all in one tx, we get a Timeout exception from + // SchemaIndexProvider.createIndexInSeparateTx .... + try (Transaction tx = graphDatabaseService.beginTx()) { + createThing(); + createSubThing(); + createSubSubThing(); + tx.success(); + } + } + + +} diff --git a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java index b3786455c..e6f9d7e6b 100644 --- a/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java +++ b/spring-data-neo4j-aspects/src/test/java/org/springframework/data/neo4j/aspects/support/typerepresentation/AbstractNodeTypeRepresentationStrategyTestBase.java @@ -25,10 +25,12 @@ import org.neo4j.graphdb.Transaction; import org.neo4j.helpers.collection.ClosableIterable; import org.neo4j.helpers.collection.IteratorUtil; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.neo4j.annotation.Indexed; import org.springframework.data.neo4j.annotation.NodeEntity; import org.springframework.data.neo4j.aspects.support.EntityTestBase; import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy; import org.springframework.data.neo4j.support.Neo4jTemplate; +import org.springframework.data.neo4j.support.index.IndexType; import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext; import org.springframework.data.neo4j.support.mapping.StoredEntityType; import org.springframework.data.neo4j.support.typerepresentation.LabelBasedNodeTypeRepresentationStrategy; @@ -59,7 +61,7 @@ public abstract class AbstractNodeTypeRepresentationStrategyTestBase extends Ent protected Thing thing; protected SubThing subThing; - protected SubThing subSubThing; + protected SubSubThing subSubThing; protected StoredEntityType thingType; protected StoredEntityType subThingType; protected StoredEntityType subSubThingType; diff --git a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context-with-initialset.xml b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context-with-initialset.xml new file mode 100644 index 000000000..fb7ebcadf --- /dev/null +++ b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context-with-initialset.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.data.neo4j.aspects.support.LabelBasedIndexedPropertyEntityTests.Thing + org.springframework.data.neo4j.aspects.support.LabelBasedIndexedPropertyEntityTests.SubThing + org.springframework.data.neo4j.aspects.support.LabelBasedIndexedPropertyEntityTests.SubSubThing + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context.xml b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context.xml new file mode 100644 index 000000000..e904cd016 --- /dev/null +++ b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/LabelBasedIndexedPropertyEntityTests-context.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml index 444a6a1c0..b1f306b11 100644 --- a/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml +++ b/spring-data-neo4j-aspects/src/test/resources/org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml @@ -33,6 +33,7 @@ + diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java index 887042769..ac67b3d84 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/config/Neo4jConfiguration.java @@ -203,6 +203,8 @@ public abstract class Neo4jConfiguration { mappingContext.setInitialEntitySet(initialEntitySet); } mappingContext.setEntityAlias(entityAlias()); + mappingContext.setIsLabelBased(nodeTypeRepresentationStrategy().isLabelBased()); + mappingContext.setFailWhenIncompatibleLabelIndexUsage(false); return mappingContext; } @@ -239,7 +241,10 @@ public abstract class Neo4jConfiguration { @Bean public IndexCreationMappingEventListener indexCreationMappingEventListener() throws Exception { - return new IndexCreationMappingEventListener(indexProvider(),schemaIndexProvider()); + return new IndexCreationMappingEventListener( + indexProvider(), + schemaIndexProvider(), + nodeTypeRepresentationStrategy()); } @Bean diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java index 8a69f131b..bb4ddc6fa 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/fieldaccess/IndexingPropertyFieldAccessorListenerFactory.java @@ -40,7 +40,7 @@ public class IndexingPropertyFieldAccessorListenerFactory implements FieldAccessorListenerFactory { + + private final PropertyFieldAccessorFactory propertyFieldAccessorFactory; + private final ConvertingNodePropertyFieldAccessorFactory convertingNodePropertyFieldAccessorFactory; + private final Neo4jTemplate template; + + public SchemaIndexingPropertyFieldAccessorListenerFactory(final Neo4jTemplate template, final PropertyFieldAccessorFactory propertyFieldAccessorFactory, final ConvertingNodePropertyFieldAccessorFactory convertingNodePropertyFieldAccessorFactory) { + this.template = template; + this.propertyFieldAccessorFactory = propertyFieldAccessorFactory; + this.convertingNodePropertyFieldAccessorFactory = convertingNodePropertyFieldAccessorFactory; + } + + @Override + public boolean accept(final Neo4jPersistentProperty property) { + return isPropertyField(property) && property.isIndexed() && property.getIndexInfo().isLabelBased(); + } + + + private boolean isPropertyField(final Neo4jPersistentProperty property) { + return propertyFieldAccessorFactory.accept(property) || convertingNodePropertyFieldAccessorFactory.accept(property); + } + + @Override + public FieldAccessListener forField(Neo4jPersistentProperty property) { + return new SchemaIndexingPropertyFieldAccessorListener(property, template); + } + + + /** + * @author Nicki Watt + * @since 09.02.2014 + */ + public static class SchemaIndexingPropertyFieldAccessorListener implements FieldAccessListener { + + private final static Logger log = LoggerFactory.getLogger(SchemaIndexingPropertyFieldAccessorListener.class); + + private final Neo4jPersistentProperty property; + private final Neo4jTemplate template; + + public SchemaIndexingPropertyFieldAccessorListener(final Neo4jPersistentProperty property, Neo4jTemplate template) { + this.property = property; + this.template = template; + } + + @Override + public void valueChanged(Object entity, Object oldVal, Object newVal) { + // Nothing to do as schema indexes are dealt with internally by Neo? + } + + } +} diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java index 19697d159..e559e353b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/mapping/IndexInfo.java @@ -53,14 +53,21 @@ public class IndexInfo { 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(); + // NW StoredEntityType not available at this stage yet .... + // only set when entity.updateStoredType(..) called StoredEntityType entityType = entity.getEntityType(); switch (annotation.level()) { case CLASS: Class declaringClass = property.getField().getDeclaringClass(); - StoredEntityType classType = entityType.findByTypeClass(declaringClass); - return classType.getAlias().toString(); + return + (entityType != null) + ? entityType.findByTypeClass(declaringClass).getAlias().toString() + : entity.getType().getSimpleName(); // Not right but not sure what to do here case INSTANCE: - return entityType.getAlias().toString(); + return + (entityType != null) + ? entityType.getAlias().toString() + : entity.getType().getSimpleName(); // Not right but not sure what to do here case GLOBAL: throw new MappingException("No global index for label based indexes"); } return entityType.getAlias().toString(); 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 index b7b63621f..9e73d82c6 100644 --- 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 @@ -119,7 +119,10 @@ public class CypherQuery implements CypherQueryDefinition { } private boolean addedStartClause(PartInfo partInfo) { - if (!partInfo.isIndexed()) return false; + boolean invalidStartClauseScenario1 = !partInfo.isIndexed(); + boolean invalidStartClauseScenario2 = partInfo.isIndexed() && partInfo.isLabelIndexed(); + + if (invalidStartClauseScenario1 || invalidStartClauseScenario2 ) return false; ListIterator it = startClauses.listIterator(); while (it.hasNext()) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartInfo.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartInfo.java index 07bdc1151..b65074bc1 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartInfo.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/repository/query/PartInfo.java @@ -59,6 +59,9 @@ public class PartInfo { return getLeafProperty().isRelationship(); } + public boolean isLabelIndexed() { + return isIndexed() && getLeafProperty().getIndexInfo().isLabelBased(); + } public boolean isIndexed() { return getLeafProperty().isIndexed(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java index 65136cbc5..7c863f257 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/Infrastructure.java @@ -22,17 +22,14 @@ import org.springframework.core.convert.ConversionService; import org.springframework.data.neo4j.conversion.ResultConverter; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.core.TypeRepresentationStrategy; -import org.springframework.data.neo4j.mapping.EntityInstantiator; import org.springframework.data.neo4j.support.index.IndexProvider; import org.springframework.data.neo4j.support.mapping.EntityRemover; import org.springframework.data.neo4j.support.mapping.EntityStateHandler; 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; import org.springframework.transaction.PlatformTransactionManager; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java index e0487a734..a0ec6312b 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructure.java @@ -22,17 +22,14 @@ import org.springframework.core.convert.ConversionService; import org.springframework.data.neo4j.conversion.ResultConverter; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.core.TypeRepresentationStrategy; -import org.springframework.data.neo4j.mapping.EntityInstantiator; import org.springframework.data.neo4j.support.index.IndexProvider; import org.springframework.data.neo4j.support.mapping.EntityRemover; import org.springframework.data.neo4j.support.mapping.EntityStateHandler; 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; import org.springframework.transaction.PlatformTransactionManager; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java index 6aed54850..58ebd27db 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/MappingInfrastructureFactoryBean.java @@ -102,7 +102,10 @@ public class MappingInfrastructureFactoryBean implements FactoryBean, Neo4jPersistentProperty>> { private IndexProvider indexProvider; private SchemaIndexProvider schemaIndexProvider; + private TypeRepresentationStrategy nodeTypeRepresentationStrategy; - public IndexCreationMappingEventListener(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider) { + public IndexCreationMappingEventListener(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider, TypeRepresentationStrategy nodeTypeRepresentationStrategy) { this.indexProvider = indexProvider; this.schemaIndexProvider = schemaIndexProvider; + this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy; } @Override @@ -46,14 +50,26 @@ public class IndexCreationMappingEventListener implements ApplicationListener entity) { final Class entityType = entity.getType(); - indexProvider.getIndex(entity, null, IndexType.SIMPLE); // TODO only when TRS is non-label? + + // TODO only when TRS is non-label? I think we prob need to do it + // anyway as you can still mix legacy indexes with label based + // ones??? + // Pass 1 - do schema based updates first to prevent locking entity.doWithProperties(new PropertyHandler() { @Override public void doWithPersistentProperty(Neo4jPersistentProperty property) { - if (!property.isIndexed()) return; - if (property.getIndexInfo().isLabelBased()) { + if (nodeTypeRepresentationStrategy.isLabelBased() && property.isIndexed() && property.getIndexInfo().isLabelBased()) { schemaIndexProvider.createIndex(property); - } else { + } + } + }); + + // Pass 2 - do everything else + indexProvider.getIndex(entity, null, IndexType.SIMPLE); + entity.doWithProperties(new PropertyHandler() { + @Override + public void doWithPersistentProperty(Neo4jPersistentProperty property) { + if (property.isIndexed() && !property.getIndexInfo().isLabelBased()){ indexProvider.getIndex(property, entityType); } } diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java index a942890c7..f7fb01d6e 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/mapping/Neo4jMappingContext.java @@ -17,7 +17,10 @@ package org.springframework.data.neo4j.support.mapping; import org.neo4j.graphdb.PropertyContainer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.data.annotation.Reference; +import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.context.AbstractMappingContext; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.MappingException; @@ -34,6 +37,8 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.*; +import static java.lang.String.format; + /** * Neo4J specific {@link MappingContext} implementation. Simply creates {@link Neo4jPersistentEntityImpl} and * {@link org.springframework.data.neo4j.mapping.Neo4jPersistentProperty} instances. @@ -42,6 +47,31 @@ import java.util.*; */ public class Neo4jMappingContext extends AbstractMappingContext, Neo4jPersistentProperty> { + private final static Logger log = LoggerFactory.getLogger(Neo4jMappingContext.class); + + // By default we don't fail but rather just output a warning + // (perhaps people have changed, or are busy changing TRS strategies + // and are happy for properties annotated as label indexes to + // resort to simple fields in this case ???) + private boolean failWhenIncompatibleLabelIndexUsage = false; + private Boolean isLabelBasedTRSInUse; + + public Boolean getIsLabelBased() { + return isLabelBasedTRSInUse; + } + + public void setIsLabelBased(Boolean labelBased) { + isLabelBasedTRSInUse = labelBased; + } + + public boolean isFailWhenIncompatibleLabelIndexUsage() { + return failWhenIncompatibleLabelIndexUsage; + } + + public void setFailWhenIncompatibleLabelIndexUsage(boolean failWhenIncompatibleLabelIndexUsage) { + this.failWhenIncompatibleLabelIndexUsage = failWhenIncompatibleLabelIndexUsage; + } + private final Map referenceAnnotations = new IdentityHashMap(); protected Neo4jPersistentEntityImpl createPersistentEntity(TypeInformation typeInformation) { @@ -60,9 +90,29 @@ public class Neo4jMappingContext extends AbstractMappingContext entity = super.addPersistentEntity(typeInformation); Collection> superTypeEntities = addSuperTypes(entity); entity.updateStoredType(new StoredEntityType(entity,superTypeEntities,entityAlias)); + doAdditionalEntityVerification(entity); return entity; } + private void doAdditionalEntityVerification(Neo4jPersistentEntityImpl entity) { + // NW-HACK checking if null for isLabelBasedTRSInUse, but should + // prob just blow up?? + if (isLabelBasedTRSInUse!= null && !isLabelBasedTRSInUse) { + entity.doWithProperties(new PropertyHandler() { + @Override + public void doWithPersistentProperty(Neo4jPersistentProperty persistentProperty) { + if (persistentProperty.isIndexed() && persistentProperty.getIndexInfo().isLabelBased()) { + if (failWhenIncompatibleLabelIndexUsage) { + throw new RuntimeException(format("Incompatible entity definition: property %s has label based index annotation however label type representation strategy not in use",persistentProperty.getName())); + } else { + log.warn("Incompatible entity definition: property {} has label based index annotation however label type representation strategy not in use - will be treated as normal property", persistentProperty.getName()); + } + } + } + }); + } + } + private List> addSuperTypes(Neo4jPersistentEntity entity) { List> entities=new ArrayList>(); final Class type = entity.getType(); diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/node/Neo4jHelper.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/node/Neo4jHelper.java index 1914a1d47..6a5977540 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/node/Neo4jHelper.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/node/Neo4jHelper.java @@ -95,6 +95,9 @@ public abstract class Neo4jHelper { } } + for (Label label: node.getLabels()) { + node.removeLabel(label); + } } for (Node node : globalGraphOperations.getAllNodes()) { try { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java index 49ac64678..150310ea9 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/schema/SchemaIndexProvider.java @@ -1,5 +1,7 @@ package org.springframework.data.neo4j.support.schema; +import org.neo4j.graphdb.Transaction; +import org.springframework.data.mapping.model.MappingException; import org.springframework.data.neo4j.annotation.QueryType; import org.springframework.data.neo4j.conversion.EndResult; import org.springframework.data.neo4j.conversion.Result; @@ -9,6 +11,9 @@ import org.springframework.data.neo4j.mapping.Neo4jPersistentEntity; import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty; import org.springframework.data.neo4j.support.query.QueryEngine; +import java.util.concurrent.*; + +import static java.lang.String.format; import static org.neo4j.helpers.collection.MapUtil.map; /** @@ -31,7 +36,51 @@ public class SchemaIndexProvider { String label = indexInfo.getIndexName(); String prop = property.getNeo4jPropertyName(); String query = indexQuery(label, prop, indexInfo.isUnique()); - Result result = cypher.query(query, null); + createIndexInSeparateTx(label,prop,query); + } + + public void createIndexInSeparateTx(final String label,final String prop, final String query) { + /* 1) NW-ISSUE01 + If we don't do this in a separate tx we get the following + error depending on certain circumstances .... : + "org.neo4j.cypher.CypherExecutionException: Cannot perform + schema updates in a transaction that has performed data updates." + + HOWEVER, even doing this does not necessarily work in all cases as + often it appears that there have been some previous updates in the + original calling thread, which itself took out some locks and then + essentially blocks this code from ever completing ... As a temp + measure introducing a timeout to catch this case rather than letting + it just hang forever (see LabelBasedIndexedPropertyHangingEntityTests) + + TODO: Look at an alternative approach / way to ensure these schema + updates are the first things done - this is not very efficient + as it stands anyway + */ + ExecutorService pool = Executors.newFixedThreadPool(1); + + try { + pool.submit(new Callable() { + @Override + public Boolean call() throws Exception { + try (Transaction tx = gd.beginTx()) { + cypher.query(query, null); + tx.success(); + } + return true; + } + }).get(2,TimeUnit.SECONDS); + pool.shutdown(); + } catch (TimeoutException e) { + throw new MappingException(format( + "Timeour occured trying to create schema index %s on against label %s: " + + "This may well be an indicator that another thread (the one which just " + + "initiated this update), has probably got a lock of " + + "this node and this timeout is because its in a deadlock situation and" + + " cant acquire it - investigation required", prop, label),e); + } catch (Exception e) { + throw new MappingException(format("Unable to create schema index %s on against label %s", prop, label),e); + } } public EndResult findAll(Neo4jPersistentEntity entity) { diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java index be1d0b76f..a8ea9524e 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/AbstractIndexBasedTypeRepresentationStrategy.java @@ -17,7 +17,6 @@ package org.springframework.data.neo4j.support.typerepresentation; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.PropertyContainer; -import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.IndexHits; import org.neo4j.helpers.collection.ClosableIterable; diff --git a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java index e66707e23..af13f50fa 100644 --- a/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java +++ b/spring-data-neo4j/src/main/java/org/springframework/data/neo4j/support/typerepresentation/TypeRepresentationStrategyFactory.java @@ -17,14 +17,10 @@ package org.springframework.data.neo4j.support.typerepresentation; import org.neo4j.graphdb.*; -import org.neo4j.graphdb.index.Index; import org.springframework.data.neo4j.core.GraphDatabase; import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy; import org.springframework.data.neo4j.core.RelationshipTypeRepresentationStrategy; -import org.springframework.data.neo4j.repository.query.CypherQuery; import org.springframework.data.neo4j.support.index.IndexProvider; -import org.springframework.data.neo4j.support.index.NoSuchIndexException; -import org.springframework.data.neo4j.support.query.QueryEngine; public class TypeRepresentationStrategyFactory { private final GraphDatabase graphDatabaseService; diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java index 738c4e07c..53ca55fbe 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/model/Person.java @@ -46,6 +46,9 @@ public class Person implements Being , Serializable { @Indexed private String nickname; + @Indexed(indexType = IndexType.LABEL, numeric = false) + private String alias; + @Indexed(indexType = IndexType.POINT, indexName="personLayer") private String wkt; @@ -327,4 +330,12 @@ public class Person implements Being , Serializable { public void addSerialFriend(Person serialFriend) { getSerialFriends().add(serialFriend); } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } } 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 index fb32f810b..c232718d1 100644 --- 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 @@ -39,13 +39,16 @@ public class TestTeam { public TestTeam createSDGTeam(GraphRepository repo, GraphRepository groupRepo, GraphRepository friendshipRepository) { emil = new Person("Emil", 30); + emil.setAlias("emilAlias"); michael = new Person("Michael", 36); michael.setBoss(emil); michael.setPersonality(Personality.EXTROVERT); michael.setLocation( "POINT(16 56)" ); + michael.setAlias("michaelAlias"); david = new Person("David", 25); + david.setAlias("davidAlias"); david.setBoss(emil); david.setLocation( 16.5, 56.5 ); friendShip = michael.knows(david); diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java index dee1ce54a..78d799e21 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/AbstractDerivedFinderMethodTestBase.java @@ -65,6 +65,9 @@ public abstract class AbstractDerivedFinderMethodTestBase { @Indexed String lastName; + @Indexed(indexType = IndexType.LABEL, numeric = false) + String alias; + String name; boolean tagged; @@ -152,6 +155,15 @@ public abstract class AbstractDerivedFinderMethodTestBase { getExpectedParams("foo")); } + @Test + public void testLabelBasedIndexQueryWithOneParam() throws Exception { + assertRepositoryQueryMethod(ThingRepository.class, + "findByAlias", + new Object[]{"foo"}, + getExpectedQuery("subclass-to-supply-trs-specific-query"), + getExpectedParams("foo")); + } + @Test public void testIndexQueryWithOneParamFullText() throws Exception { assertRepositoryQueryMethod(ThingRepository.class, diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForIndexedBasedTRSTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForIndexedBasedTRSTests.java index c3b1786ca..7ec4072bb 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForIndexedBasedTRSTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForIndexedBasedTRSTests.java @@ -28,6 +28,7 @@ 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.TransactionalTestExecutionListener; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.concurrent.TimeUnit; @@ -35,6 +36,8 @@ import java.util.concurrent.TimeUnit; import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; /** * Tests for the various finder method based scenarios @@ -91,6 +94,29 @@ public class DerivedFinderMethodForIndexedBasedTRSTests extends AbstractDerivedF super.testIndexQueryWithOneParam(); } + @Test + @Override + public void testLabelBasedIndexQueryWithOneParam() throws Exception { + + // Ensure mappingContext set NOT to fail (but rather warn) + // when incompatibility detected + assertFalse(ctx.isFailWhenIncompatibleLabelIndexUsage()); + + /* + Is this the correct logic??? - what should we do when a derived + field has been marked as a label based indexed field and + we have a legacy based indexing strategy in play. Have currently + added in properties failWhenIncompatibleLabelIndexUsage and + isLabelBasedTRSInUse to Neo4jMappingContext to help + + */ + // "findByAlias", + this.trsSpecificExpectedQuery = DEFAULT_START_CLAUSE + + " WHERE `thing`.`alias` = {0} RETURN `thing`"; + this.trsSpecificExpectedParams = new Object[] { "foo" }; + super.testLabelBasedIndexQueryWithOneParam(); + } + @Test @Override public void testIndexQueryWithOneParamFullText() throws Exception { @@ -362,4 +388,10 @@ public class DerivedFinderMethodForIndexedBasedTRSTests extends AbstractDerivedF super.testFindByNumericIndexedField(); } + @Test + @Transactional + public void testMultipleIndexedFields() throws Exception { + super.testMultipleIndexedFields(); + } + } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java index 600e97200..29275b94b 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/DerivedFinderMethodForLabelBasedTRSTests.java @@ -32,6 +32,7 @@ import java.util.Date; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static org.junit.Assert.fail; /** * Tests for the various finder method based scenarios @@ -366,4 +367,15 @@ public class DerivedFinderMethodForLabelBasedTRSTests extends AbstractDerivedFin this.trsSpecificExpectedQuery = "START `thing`=node:`Thing`(`number`={0}) RETURN `thing`"; super.testFindByNumericIndexedField(); } + + @Test + @Override + public void testLabelBasedIndexQueryWithOneParam() throws Exception { + // findByAlias + this.trsSpecificExpectedQuery = DEFAULT_MATCH_CLAUSE + + " WHERE `thing`.`alias` = {0}" + + " RETURN `thing`"; + this.trsSpecificExpectedParams = new Object[] { "foo" }; + super.testLabelBasedIndexQueryWithOneParam(); + } } diff --git a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/ThingRepository.java b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/ThingRepository.java index c030cade0..527db3a52 100644 --- a/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/ThingRepository.java +++ b/spring-data-neo4j/src/test/java/org/springframework/data/neo4j/repository/query/ThingRepository.java @@ -55,4 +55,7 @@ public interface ThingRepository extends GraphRepository