DATAGRAPH-388 : First stab at getting Schema Indexes (Label based) in

This commit is contained in:
Nicki Watt
2014-02-09 23:38:55 +00:00
committed by Michael Hunger
parent 02e4257c68
commit 8ebcabdfb2
30 changed files with 1061 additions and 26 deletions

View File

@@ -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<String> names = executeQuery("Thing","schemaIndexedThingName","thing-theSchemaIndexedThingName");
assertThat( names , containsInAnyOrder( "thing" ));
}
@Test
public void testAbleToQueryUsingSchemaIndexAgainstHierarchy() {
Collection<String> names1 = executeQuery( "Thing", "schemaIndexedThingName","thing-theSchemaIndexedThingName");
assertThat( names1 , containsInAnyOrder( "thing" ));
Collection<String> names2 = executeQuery( "Thing", "schemaIndexedThingName","subThing-theSchemaIndexedThingName");
assertThat( names2 , containsInAnyOrder( "subThing" ));
Collection<String> names3 = executeQuery( "Thing", "schemaIndexedThingName","subSubThing-theSchemaIndexedThingName");
assertThat( names3 , containsInAnyOrder( "subSubThing" ));
}
@Test
public void testAbleToQueryAndFindCommonValuesUsingSchemaIndexAcrossHierarchy() {
Collection<String> names1 = executeQuery( "Thing", "schemaIndexedCommonName","common");
assertThat( names1 , containsInAnyOrder( "thing" ,"subThing" ,"subSubThing" ));
}
private Collection<String> executeQuery(String label, String indexedPropName, String indexedPropValue) {
Map<String,Object> params = new HashMap<String,Object>();
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<String> 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<IndexDefinition> idefs = graphDatabaseService.schema().getIndexes(DynamicLabel.label(label));
Set<String> propNames2Find = new HashSet<String>();
Set<String> extraPropNames = new HashSet<String>();
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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;

View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
<context:component-scan base-package="org.springframework.data.neo4j.aspects">
<context:exclude-filter expression=".*_Roo_.*" type="regex"/>
<context:exclude-filter expression="org.springframework.context.annotation.Configuration" type="annotation"/>
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<bean id="neo4jNodeBacking" class="org.springframework.data.neo4j.aspects.support.node.Neo4jNodeBacking" factory-method="aspectOf">
<property name="template" ref="template"/>
<property name="nodeEntityStateFactory" ref="nodeEntityStateFactory"/>
</bean>
<bean class="org.springframework.data.neo4j.aspects.support.relationship.Neo4jRelationshipBacking" factory-method="aspectOf">
<property name="template" ref="template"/>
<property name="relationshipEntityStateFactory" ref="relationshipEntityStateFactory"/>
</bean>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown" scope="singleton"/>
<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" />
<constructor-arg ref="nodeTypeRepresentationStrategy" />
</bean>
<bean id="mappingInfrastructure" class="org.springframework.data.neo4j.support.MappingInfrastructureFactoryBean">
<property name="graphDatabaseService" ref="graphDatabaseService"/>
<property name="conversionService" ref="conversionService"/>
<property name="mappingContext" ref="mappingContext"/>
<property name="entityStateHandler" ref="entityStateHandler"/>
<property name="nodeTypeRepresentationStrategy" ref="nodeTypeRepresentationStrategy"/>
<property name="nodeEntityStateFactory" ref="nodeEntityStateFactory"/>
<property name="nodeEntityInstantiator" ref="graphEntityInstantiator"/>
<property name="relationshipTypeRepresentationStrategy" ref="relationshipTypeRepresentationStrategy"/>
<property name="relationshipEntityStateFactory" ref="relationshipEntityStateFactory"/>
<property name="relationshipEntityInstantiator" ref="relationshipEntityInstantiator"/>
<property name="validator">
<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
</property>
</bean>
<bean id="template" class="org.springframework.data.neo4j.support.Neo4jTemplate">
<constructor-arg name="infrastructure" ref="mappingInfrastructure"/>
</bean>
<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"/>
<constructor-arg ref="graphDatabase"/>
</bean>
<bean id="graphDatabase" class="org.springframework.data.neo4j.support.DelegatingGraphDatabase">
<constructor-arg ref="graphDatabaseService"/>
</bean>
<bean id="relationshipEntityInstantiator" class="org.springframework.data.neo4j.support.relationship.RelationshipEntityInstantiator">
<constructor-arg ref="entityStateHandler"/>
</bean>
<bean id="graphEntityInstantiator" class="org.springframework.data.neo4j.support.node.NodeEntityInstantiator">
<constructor-arg ref="entityStateHandler"/>
</bean>
<bean id="typeRepresentationStrategyFactory" class="org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory">
<constructor-arg ref="graphDatabase"/>
</bean>
<bean id="nodeTypeRepresentationStrategy" factory-bean="typeRepresentationStrategyFactory" factory-method="getNodeTypeRepresentationStrategy" />
<bean id="relationshipTypeRepresentationStrategy" factory-bean="typeRepresentationStrategyFactory" factory-method="getRelationshipTypeRepresentationStrategy"/>
<bean id="nodeEntityStateFactory" class="org.springframework.data.neo4j.support.node.NodeEntityStateFactory">
<constructor-arg ref="mappingContext"/>
<constructor-arg>
<bean class="org.springframework.data.neo4j.fieldaccess.NodeDelegatingFieldAccessorFactory$Factory"/>
</constructor-arg>
</bean>
<bean id="mappingContext" class="org.springframework.data.neo4j.support.mapping.Neo4jMappingContext" >
<!-- NW-ISSUE01 Possibility for getting around some of the schema vs data tx issues
is to set the initialEntitySet upfront, however its a pain to do this
manually would probably be better if we could detect all the entities
at startup rather than requiring them to be explicitly set here ... -->
<property name="initialEntitySet" >
<set>
<value>org.springframework.data.neo4j.aspects.support.LabelBasedIndexedPropertyEntityTests.Thing</value>
<value>org.springframework.data.neo4j.aspects.support.LabelBasedIndexedPropertyEntityTests.SubThing</value>
<value>org.springframework.data.neo4j.aspects.support.LabelBasedIndexedPropertyEntityTests.SubSubThing</value>
</set>
</property>
</bean>
<bean id="relationshipEntityStateFactory" class="org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory">
<constructor-arg ref="mappingContext"/>
<constructor-arg>
<bean class="org.springframework.data.neo4j.fieldaccess.RelationshipDelegatingFieldAccessorFactory$Factory"/>
</constructor-arg>
</bean>
<bean id="transactionManager" class="org.springframework.data.neo4j.config.JtaTransactionManagerFactoryBean">
<constructor-arg ref="graphDatabaseService"/>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- Adds dependency checks for setters annotated with @Required -->
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" />
<bean class="org.springframework.data.neo4j.support.Neo4jExceptionTranslator"/>
</beans>

View File

@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
<context:component-scan base-package="org.springframework.data.neo4j.aspects">
<context:exclude-filter expression=".*_Roo_.*" type="regex"/>
<context:exclude-filter expression="org.springframework.context.annotation.Configuration" type="annotation"/>
<context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation"/>
</context:component-scan>
<bean id="neo4jNodeBacking" class="org.springframework.data.neo4j.aspects.support.node.Neo4jNodeBacking" factory-method="aspectOf">
<property name="template" ref="template"/>
<property name="nodeEntityStateFactory" ref="nodeEntityStateFactory"/>
</bean>
<bean class="org.springframework.data.neo4j.aspects.support.relationship.Neo4jRelationshipBacking" factory-method="aspectOf">
<property name="template" ref="template"/>
<property name="relationshipEntityStateFactory" ref="relationshipEntityStateFactory"/>
</bean>
<bean id="graphDatabaseService" class="org.neo4j.test.ImpermanentGraphDatabase" destroy-method="shutdown" scope="singleton"/>
<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" />
<constructor-arg ref="nodeTypeRepresentationStrategy" />
</bean>
<bean id="mappingInfrastructure" class="org.springframework.data.neo4j.support.MappingInfrastructureFactoryBean">
<property name="graphDatabaseService" ref="graphDatabaseService"/>
<property name="conversionService" ref="conversionService"/>
<property name="mappingContext" ref="mappingContext"/>
<property name="entityStateHandler" ref="entityStateHandler"/>
<property name="nodeTypeRepresentationStrategy" ref="nodeTypeRepresentationStrategy"/>
<property name="nodeEntityStateFactory" ref="nodeEntityStateFactory"/>
<property name="nodeEntityInstantiator" ref="graphEntityInstantiator"/>
<property name="relationshipTypeRepresentationStrategy" ref="relationshipTypeRepresentationStrategy"/>
<property name="relationshipEntityStateFactory" ref="relationshipEntityStateFactory"/>
<property name="relationshipEntityInstantiator" ref="relationshipEntityInstantiator"/>
<property name="validator">
<bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
</property>
</bean>
<bean id="template" class="org.springframework.data.neo4j.support.Neo4jTemplate">
<constructor-arg name="infrastructure" ref="mappingInfrastructure"/>
</bean>
<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"/>
<constructor-arg ref="graphDatabase"/>
</bean>
<bean id="graphDatabase" class="org.springframework.data.neo4j.support.DelegatingGraphDatabase">
<constructor-arg ref="graphDatabaseService"/>
</bean>
<bean id="relationshipEntityInstantiator" class="org.springframework.data.neo4j.support.relationship.RelationshipEntityInstantiator">
<constructor-arg ref="entityStateHandler"/>
</bean>
<bean id="graphEntityInstantiator" class="org.springframework.data.neo4j.support.node.NodeEntityInstantiator">
<constructor-arg ref="entityStateHandler"/>
</bean>
<bean id="typeRepresentationStrategyFactory" class="org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory">
<constructor-arg ref="graphDatabase"/>
</bean>
<bean id="nodeTypeRepresentationStrategy" factory-bean="typeRepresentationStrategyFactory" factory-method="getNodeTypeRepresentationStrategy" />
<bean id="relationshipTypeRepresentationStrategy" factory-bean="typeRepresentationStrategyFactory" factory-method="getRelationshipTypeRepresentationStrategy"/>
<bean id="nodeEntityStateFactory" class="org.springframework.data.neo4j.support.node.NodeEntityStateFactory">
<constructor-arg ref="mappingContext"/>
<constructor-arg>
<bean class="org.springframework.data.neo4j.fieldaccess.NodeDelegatingFieldAccessorFactory$Factory"/>
</constructor-arg>
</bean>
<bean id="mappingContext" class="org.springframework.data.neo4j.support.mapping.Neo4jMappingContext" >
<!-- NW-ISSUE01 Possibility for getting around some of the schema vs data tx issues
is to set the initialEntitySet upfront, however its a pain to do this
manually would probably be better if we could detect all the entities
at startup rather than requiring them to be explicitly set here ... -->
<!--property name="initialEntitySet" >
<set>
<value>org.springframework.data.neo4j.aspects.support.typerepresentation.AbstractNodeTypeRepresentationStrategyTestBase.Thing</value>
<value>org.springframework.data.neo4j.aspects.support.typerepresentation.AbstractNodeTypeRepresentationStrategyTestBase.SubThing</value>
<value>org.springframework.data.neo4j.aspects.support.typerepresentation.AbstractNodeTypeRepresentationStrategyTestBase.SubSubThing</value>
</set>
</property-->
</bean>
<bean id="relationshipEntityStateFactory" class="org.springframework.data.neo4j.support.relationship.RelationshipEntityStateFactory">
<constructor-arg ref="mappingContext"/>
<constructor-arg>
<bean class="org.springframework.data.neo4j.fieldaccess.RelationshipDelegatingFieldAccessorFactory$Factory"/>
</constructor-arg>
</bean>
<bean id="transactionManager" class="org.springframework.data.neo4j.config.JtaTransactionManagerFactoryBean">
<constructor-arg ref="graphDatabaseService"/>
</bean>
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- Adds dependency checks for setters annotated with @Required -->
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" />
<bean class="org.springframework.data.neo4j.support.Neo4jExceptionTranslator"/>
</beans>

View File

@@ -33,6 +33,7 @@
<bean id="indexCreationMappingEventListener" class="org.springframework.data.neo4j.support.mapping.IndexCreationMappingEventListener">
<constructor-arg ref="indexProvider" />
<constructor-arg ref="schemaIndexProvider" />
<constructor-arg ref="nodeTypeRepresentationStrategy" />
</bean>

View File

@@ -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

View File

@@ -40,7 +40,7 @@ public class IndexingPropertyFieldAccessorListenerFactory<S extends PropertyCont
@Override
public boolean accept(final Neo4jPersistentProperty property) {
return isPropertyField(property) && property.isIndexed();
return isPropertyField(property) && property.isIndexed() && !property.getIndexInfo().isLabelBased();
}

View File

@@ -39,6 +39,10 @@ public class NodeDelegatingFieldAccessorFactory extends DelegatingFieldAccessorF
template,
new PropertyFieldAccessorFactory(template),
new ConvertingNodePropertyFieldAccessorFactory(template)),
new SchemaIndexingPropertyFieldAccessorListenerFactory(
template,
new PropertyFieldAccessorFactory(template),
new ConvertingNodePropertyFieldAccessorFactory(template)),
new ValidatingNodePropertyFieldAccessorListenerFactory(template)
);
}

View File

@@ -0,0 +1,79 @@
/**
* 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.fieldaccess;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.index.Index;
import org.neo4j.index.lucene.ValueContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.neo4j.mapping.Neo4jPersistentProperty;
import org.springframework.data.neo4j.support.Neo4jTemplate;
public class SchemaIndexingPropertyFieldAccessorListenerFactory<S extends PropertyContainer, T> 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<T extends PropertyContainer> 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?
}
}
}

View File

@@ -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();

View File

@@ -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<StartClause> it = startClauses.listIterator();
while (it.hasNext()) {

View File

@@ -59,6 +59,9 @@ public class PartInfo {
return getLeafProperty().isRelationship();
}
public boolean isLabelIndexed() {
return isIndexed() && getLeafProperty().getIndexInfo().isLabelBased();
}
public boolean isIndexed() {
return getLeafProperty().isIndexed();

View File

@@ -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;

View File

@@ -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;

View File

@@ -102,7 +102,10 @@ public class MappingInfrastructureFactoryBean implements FactoryBean<Infrastruct
@Override
public void afterPropertiesSet() {
try {
if (this.mappingContext == null) this.mappingContext = new Neo4jMappingContext();
if (this.mappingContext == null) {
this.mappingContext = new Neo4jMappingContext();
this.mappingContext.setIsLabelBased(false);
}
if (this.isNewStrategyFactory == null) {
this.isNewStrategyFactory = new MappingContextIsNewStrategyFactory(mappingContext);
}

View File

@@ -23,7 +23,6 @@ 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;

View File

@@ -15,9 +15,11 @@
*/
package org.springframework.data.neo4j.support.mapping;
import org.neo4j.graphdb.Node;
import org.springframework.context.ApplicationListener;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.context.MappingContextEvent;
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.index.IndexProvider;
@@ -31,10 +33,12 @@ import org.springframework.data.neo4j.support.schema.SchemaIndexProvider;
public class IndexCreationMappingEventListener implements ApplicationListener<MappingContextEvent<Neo4jPersistentEntity<?>, Neo4jPersistentProperty>> {
private IndexProvider indexProvider;
private SchemaIndexProvider schemaIndexProvider;
private TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy;
public IndexCreationMappingEventListener(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider) {
public IndexCreationMappingEventListener(IndexProvider indexProvider, SchemaIndexProvider schemaIndexProvider, TypeRepresentationStrategy<Node> nodeTypeRepresentationStrategy) {
this.indexProvider = indexProvider;
this.schemaIndexProvider = schemaIndexProvider;
this.nodeTypeRepresentationStrategy = nodeTypeRepresentationStrategy;
}
@Override
@@ -46,14 +50,26 @@ public class IndexCreationMappingEventListener implements ApplicationListener<Ma
private void ensureEntityIndexes(Neo4jPersistentEntity<?> 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<Neo4jPersistentProperty>() {
@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<Neo4jPersistentProperty>() {
@Override
public void doWithPersistentProperty(Neo4jPersistentProperty property) {
if (property.isIndexed() && !property.getIndexInfo().isLabelBased()){
indexProvider.getIndex(property, entityType);
}
}

View File

@@ -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<Neo4jPersistentEntityImpl<?>, 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<Annotation, Boolean> referenceAnnotations = new IdentityHashMap<Annotation, java.lang.Boolean>();
protected <T> Neo4jPersistentEntityImpl<?> createPersistentEntity(TypeInformation<T> typeInformation) {
@@ -60,9 +90,29 @@ public class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersistentE
final Neo4jPersistentEntityImpl<?> entity = super.addPersistentEntity(typeInformation);
Collection<Neo4jPersistentEntity<?>> 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<Neo4jPersistentProperty>() {
@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<Neo4jPersistentEntity<?>> addSuperTypes(Neo4jPersistentEntity<?> entity) {
List<Neo4jPersistentEntity<?>> entities=new ArrayList<Neo4jPersistentEntity<?>>();
final Class<?> type = entity.getType();

View File

@@ -95,6 +95,9 @@ public abstract class Neo4jHelper {
}
}
for (Label label: node.getLabels()) {
node.removeLabel(label);
}
}
for (Node node : globalGraphOperations.getAllNodes()) {
try {

View File

@@ -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<Object> 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<Boolean>() {
@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 <T> EndResult<T> findAll(Neo4jPersistentEntity entity) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -39,13 +39,16 @@ public class TestTeam {
public TestTeam createSDGTeam(GraphRepository<Person> repo, GraphRepository<Group> groupRepo, GraphRepository<Friendship> 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);

View File

@@ -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,

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -55,4 +55,7 @@ public interface ThingRepository extends GraphRepository<AbstractDerivedFinderMe
Thing findByBornAfter(Date date);
Thing findById(long id);
Thing findByOwnerId(long id);
// Label based indexes
Thing findByAlias(String alias);
}