DATAGRAPH-387 : LabelingNodeTypeRepresentationStrategy (phase 1)

This commit is contained in:
Nicki Watt
2013-09-24 19:40:14 +01:00
committed by Michael Hunger
parent a44acd6614
commit aac1a16e9e
11 changed files with 523 additions and 1 deletions

View File

@@ -0,0 +1,183 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.aspects.support.typerepresentation;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Transaction;
import org.neo4j.helpers.collection.IteratorUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.aspects.support.EntityTestBase;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import org.springframework.data.neo4j.support.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
import org.springframework.data.neo4j.support.typerepresentation.CoreAPIBasedLabelingNodeTypeRepresentationStrategy;
import org.springframework.test.context.CleanContextCacheTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.HashSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:org/springframework/data/neo4j/aspects/support/Neo4jGraphPersistenceTests-context.xml",
"classpath:org/springframework/data/neo4j/aspects/support/LabelingTypeRepresentationStrategyOverride-context.xml"})
@TestExecutionListeners({CleanContextCacheTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, TransactionalTestExecutionListener.class})
public class LabelingNodeTypeRepresentationStrategyTests extends EntityTestBase {
@Autowired
private CoreAPIBasedLabelingNodeTypeRepresentationStrategy nodeTypeRepresentationStrategy;
@Autowired
Neo4jTemplate neo4jTemplate;
@Autowired
Neo4jMappingContext ctx;
private Thing thing;
private SubThing subThing;
private StoredEntityType thingType;
private StoredEntityType subThingType;
@BeforeTransaction
public void cleanDb() {
super.cleanDb();
}
@Before
public void setUp() throws Exception {
if (thing == null) {
createThingsAndLinks();
}
thingType = typeOf(Thing.class);
subThingType = typeOf(SubThing.class);
}
@Test
@Transactional
public void testPostEntityCreation() throws Exception {
// Anything to test here??
}
@Test
public void testPreEntityRemoval() throws Exception {
// preEntityRemoval is a no op method, so nothing to test here!
}
@Test
@Transactional
public void testFindAll() throws Exception {
assertEquals("Did not find all things.",
new HashSet<PropertyContainer>(Arrays.asList(neo4jTemplate.getPersistentState(subThing), neo4jTemplate.getPersistentState(thing))),
IteratorUtil.addToCollection(nodeTypeRepresentationStrategy.findAll(thingType), new HashSet<Node>()));
}
@Test
@Transactional
public void testCount() throws Exception {
assertEquals(2, nodeTypeRepresentationStrategy.count(thingType));
}
@Test
@Transactional
public void testGetJavaType() throws Exception {
assertEquals(thingType.getAlias(), nodeTypeRepresentationStrategy.readAliasFrom(node(thing)));
assertEquals(subThingType.getAlias(), nodeTypeRepresentationStrategy.readAliasFrom(node(subThing)));
assertEquals(Thing.class, neo4jTemplate.getStoredJavaType(node(thing)));
assertEquals(SubThing.class, neo4jTemplate.getStoredJavaType(node(subThing)));
}
@Test
@Transactional
public void testCreateEntityAndInferType() throws Exception {
Thing newThing = neo4jTemplate.createEntityFromStoredType(node(thing), neo4jTemplate.getMappingPolicy(thing));
assertEquals(thing, newThing);
}
@Test
@Transactional
public void testCreateEntityAndSpecifyType() throws Exception {
Thing newThing = neo4jTemplate.createEntityFromState(node(subThing), Thing.class, neo4jTemplate.getMappingPolicy(subThing));
assertEquals(subThing, newThing);
}
@Test
@Transactional
public void testProjectEntity() throws Exception {
Unrelated other = neo4jTemplate.projectTo(node(thing), Unrelated.class);
assertEquals("thing", other.getName());
}
private Node node(Thing thing) {
return getNodeState(thing);
}
private Thing createThingsAndLinks() {
Transaction tx = graphDatabaseService.beginTx();
try {
Node n1 = graphDatabaseService.createNode();
thing = neo4jTemplate.setPersistentState(new Thing(),n1);
nodeTypeRepresentationStrategy.writeTypeTo(n1, neo4jTemplate.getEntityType(Thing.class));
thing.setName("thing");
Node n2 = graphDatabaseService.createNode();
subThing = neo4jTemplate.setPersistentState(new SubThing(),n2);
nodeTypeRepresentationStrategy.writeTypeTo(n2, neo4jTemplate.getEntityType(SubThing.class));
subThing.setName("subThing");
tx.success();
return thing;
} finally {
tx.finish();
}
}
@NodeEntity
public static class Unrelated {
String name;
public String getName() {
return name;
}
}
@NodeEntity
public static class Thing {
String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static class SubThing extends Thing {
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="typeRepresentationStrategyFactory" class="org.springframework.data.neo4j.support.typerepresentation.TypeRepresentationStrategyFactory">
<constructor-arg index="0" ref="graphDatabase"/>
<constructor-arg index="1" value="Labeled"/>
</bean>
</beans>

View File

@@ -33,6 +33,8 @@ import org.springframework.data.neo4j.annotation.QueryType;
import org.springframework.data.neo4j.conversion.DefaultConverter;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.GraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.support.DelegatingGraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter;
import org.springframework.data.neo4j.support.query.QueryEngine;
@@ -46,6 +48,7 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat
}
private ConversionService conversionService;
private ResultConverter resultConverter;
private GraphDatabaseGlobalOperations globalOperations;
public SpringRestGraphDatabase( RestAPI api){
super(api);
@@ -59,6 +62,14 @@ public class SpringRestGraphDatabase extends org.neo4j.rest.graphdb.RestGraphDat
this(new RestAPIFacade( uri, user, password ));
}
@Override
public GraphDatabaseGlobalOperations getGlobalGraphOperations() {
if (this.globalOperations == null) {
this.globalOperations = new DelegatingGraphDatabaseGlobalOperations(this);
}
return globalOperations;
}
@Override
public Node createNode(Map<String, Object> props) {
return super.getRestAPI().createNode(props);

View File

@@ -30,6 +30,13 @@ import java.util.Map;
public interface GraphDatabase {
/**
* @return an object which is able to provide global graph
* type operations
*/
GraphDatabaseGlobalOperations getGlobalGraphOperations();
/**
* @return the reference node of the underlying graph database
*/

View File

@@ -0,0 +1,19 @@
package org.springframework.data.neo4j.core;
import org.neo4j.graphdb.*;
/**
* Defines the list of global graph operations which can be used
* within SDN. (At present restricted to those providing Label
* based functionality)
*
* @author Nicki Watt
* @since 24-09-2013
*/
public interface GraphDatabaseGlobalOperations {
public ResourceIterable<Label> getAllLabels();
public ResourceIterable<Node> getAllNodesWithLabel(Label label);
}

View File

@@ -33,6 +33,7 @@ import org.springframework.data.neo4j.conversion.DefaultConverter;
import org.springframework.data.neo4j.conversion.Result;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.GraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.support.index.IndexType;
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
import org.springframework.data.neo4j.support.query.ConversionServiceQueryResultConverter;
@@ -55,6 +56,7 @@ public class DelegatingGraphDatabase implements GraphDatabase {
private static final Logger log = LoggerFactory.getLogger(DelegatingGraphDatabase.class);
protected GraphDatabaseGlobalOperations globalOperations;
protected GraphDatabaseService delegate;
private ConversionService conversionService;
private ResultConverter resultConverter;
@@ -62,17 +64,23 @@ public class DelegatingGraphDatabase implements GraphDatabase {
private volatile QueryEngine<Object> gremlinQueryEngine;
public DelegatingGraphDatabase(final GraphDatabaseService delegate) {
this.delegate = delegate;
this(delegate,null);
}
public DelegatingGraphDatabase(final GraphDatabaseService delegate, ResultConverter resultConverter) {
this.delegate = delegate;
this.resultConverter = resultConverter;
this.globalOperations = new DelegatingGraphDatabaseGlobalOperations(delegate);
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
@Override
public GraphDatabaseGlobalOperations getGlobalGraphOperations() {
return globalOperations;
}
@Override
public void setResultConverter(ResultConverter resultConverter) {
this.resultConverter = resultConverter;

View File

@@ -0,0 +1,36 @@
package org.springframework.data.neo4j.support;
import org.neo4j.graphdb.*;
import org.neo4j.tooling.GlobalGraphOperations;
import org.springframework.data.neo4j.core.GraphDatabaseGlobalOperations;
/**
* @author Nicki Watt
* @since 24-09-2013
*/
public class DelegatingGraphDatabaseGlobalOperations implements GraphDatabaseGlobalOperations {
private GraphDatabaseService graphDatabaseService;
private GlobalGraphOperations globalGraphOperations;
public DelegatingGraphDatabaseGlobalOperations(GraphDatabaseService delegate) {
this.graphDatabaseService = delegate;
}
private GlobalGraphOperations getGlobalGraphOperations() {
if (globalGraphOperations == null) {
globalGraphOperations = GlobalGraphOperations.at(graphDatabaseService);
}
return globalGraphOperations;
}
@Override
public ResourceIterable<Label> getAllLabels() {
return getGlobalGraphOperations().getAllLabels();
}
@Override
public ResourceIterable<Node> getAllNodesWithLabel(Label label) {
return getGlobalGraphOperations().getAllNodesWithLabel(label);
}
}

View File

@@ -0,0 +1,30 @@
package org.springframework.data.neo4j.support.mapping;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterable;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.helpers.collection.ClosableIterable;
import java.util.Iterator;
/**
* @author Nicki Watt
* @since 24-09-2013
*/
public class ResourceIterableClosableIterable implements ClosableIterable {
private ResourceIterator iterator;
public ResourceIterableClosableIterable(ResourceIterable<Node> resourceIterable) {
this.iterator = resourceIterable.iterator();
}
@Override
public void close() {
iterator.close();
}
@Override
public Iterator iterator() {
return iterator;
}
};

View File

@@ -0,0 +1,120 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.typerepresentation;
import org.neo4j.graphdb.DynamicLabel;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterable;
import org.neo4j.helpers.collection.ClosableIterable;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.GraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.mapping.ResourceIterableClosableIterable;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
/**
* Provides a Node Type Representation Strategy which makes use of Labels, and specifically
* uses the Core API as the mechanism for dealing with this. (This is inline with how
* the original Node Type Representation Strategies used to work - moving forward a
* Cypher based one will be used, however this exists for comparison purposes at this
* point in time in the development)
*
* @author Nicki Watt
* @since 24-09-2013
*/
public class CoreAPIBasedLabelingNodeTypeRepresentationStrategy implements NodeTypeRepresentationStrategy {
// option A : store type in entity
// public static final String TYPE_PROPERTY_NAME = "__type__";
// option B : add special type label
public static final String TYPE_LABEL_PREFIX = "__TYPE__";
protected GraphDatabase graphDb;
protected final Class<Node> clazz;
public CoreAPIBasedLabelingNodeTypeRepresentationStrategy(GraphDatabase graphDb) {
this.graphDb = graphDb;
this.clazz = Node.class;
}
@Override
public void writeTypeTo(Node state, StoredEntityType type) {
if (type == null || !type.isNodeEntity()) return;
ResourceIterable<Label> labels = state.getLabels();
if (labels.iterator().hasNext()) {
return; // already there
}
addLabel(state,type,true);
for (StoredEntityType superType : type.getSuperTypes()) {
addLabel(state, superType, false);
}
}
private void addLabel(Node state, StoredEntityType type, boolean isPrimary) {
String alias = type.getAlias().toString();
state.addLabel(DynamicLabel.label(alias));
if (isPrimary) {
// option A : store type in entity
// state.setProperty(TYPE_PROPERTY_NAME,alias);
// option B : add special type label
state.addLabel(DynamicLabel.label(TYPE_LABEL_PREFIX + alias));
}
}
@Override
public <U> ClosableIterable<Node> findAll(StoredEntityType type) {
long count = 0;
GraphDatabaseGlobalOperations globalOps = graphDb.getGlobalGraphOperations();
final ResourceIterable<Node> rin = globalOps.getAllNodesWithLabel(DynamicLabel.label(type.getAlias().toString()));
return new ResourceIterableClosableIterable(rin);
}
@Override
public long count(StoredEntityType type) {
long count = 0;
GraphDatabaseGlobalOperations globalOps = graphDb.getGlobalGraphOperations();
for (Node n : globalOps.getAllNodesWithLabel(DynamicLabel.label(type.getAlias().toString()))) {
count++;
}
return count;
}
@Override
public Object readAliasFrom(Node state) {
if (state == null)
throw new IllegalArgumentException("Node is null");
// Option A: Derive alias from property
// return state.getProperty(TYPE_PROPERTY_NAME);
// Option B: Derive alias from special Label
for (Label label: state.getLabels()) {
if (label.name().startsWith(TYPE_LABEL_PREFIX)) {
return label.name().substring(TYPE_LABEL_PREFIX.length());
}
}
throw new IllegalStateException("No primary SDN label exists .. (i.e one with SDN_) ");
}
@Override
public void preEntityRemoval(Node state) {
// don't think we need to do anything here!
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.support.typerepresentation;
import org.neo4j.graphdb.Node;
import org.neo4j.helpers.collection.ClosableIterable;
import org.springframework.data.neo4j.core.GraphDatabase;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
/**
* Provides a Node Type Representation Strategy which makes use of Labels, and specifically
* uses Cypher as the mechanism for dealing with this.
*
* @author Nicki Watt
* @since 24-09-2013
*/
public class CypherBasedLabelingNodeTypeRepresentationStrategy implements NodeTypeRepresentationStrategy {
// option A : store type in entity
// public static final String TYPE_PROPERTY_NAME = "__type__";
// option B : add special type label
public static final String TYPE_LABEL_PREFIX = "__TYPE__";
protected GraphDatabase graphDb;
protected final Class<Node> clazz;
public CypherBasedLabelingNodeTypeRepresentationStrategy(GraphDatabase graphDb) {
this.graphDb = graphDb;
this.clazz = Node.class;
}
@Override
public void writeTypeTo(Node state, StoredEntityType type) {
throw new RuntimeException("TODO");
}
private void addLabel(Node state, StoredEntityType type, boolean isPrimary) {
throw new RuntimeException("TODO");
}
@Override
public <U> ClosableIterable<Node> findAll(StoredEntityType type) {
throw new RuntimeException("TODO");
}
@Override
public long count(StoredEntityType type) {
throw new RuntimeException("TODO");
}
@Override
public Object readAliasFrom(Node state) {
throw new RuntimeException("TODO");
}
@Override
public void preEntityRemoval(Node state) {
// don't think we need to do anything here!
}
}

View File

@@ -19,6 +19,7 @@ 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.GraphDatabaseGlobalOperations;
import org.springframework.data.neo4j.core.NodeTypeRepresentationStrategy;
import org.springframework.data.neo4j.core.RelationshipTypeRepresentationStrategy;
import org.springframework.data.neo4j.support.index.IndexProvider;
@@ -53,12 +54,24 @@ public class TypeRepresentationStrategyFactory {
try {
if (isAlreadyIndexed(graphDatabaseService)) return Strategy.Indexed;
if (isAlreadySubRef(graphDatabaseService)) return Strategy.SubRef;
if (isAlreadyLabeled(graphDatabaseService)) return Strategy.Labeled;
return Strategy.Indexed;
} finally {
tx.success();tx.finish();
}
}
private static boolean isAlreadyLabeled(GraphDatabase graphDatabaseService) {
GraphDatabaseGlobalOperations globalOps = graphDatabaseService.getGlobalGraphOperations();
try {
return globalOps.getAllLabels().iterator().hasNext();
} catch (UnsupportedOperationException e) {
// Currently the REST DB does not support global ops
// TODO : Look to change REST project to support it
return false;
}
}
private static boolean isAlreadyIndexed(GraphDatabase graphDatabaseService) {
try {
final Index<PropertyContainer> index = graphDatabaseService.getIndex(IndexingNodeTypeRepresentationStrategy.INDEX_NAME);
@@ -105,6 +118,17 @@ public class TypeRepresentationStrategyFactory {
return new NoopRelationshipTypeRepresentationStrategy();
}
},
Labeled {
@Override
public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) {
return new CoreAPIBasedLabelingNodeTypeRepresentationStrategy(graphDatabaseService);
}
@Override
public RelationshipTypeRepresentationStrategy getRelationshipTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) {
return new NoopRelationshipTypeRepresentationStrategy();
}
},
Indexed {
@Override
public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy(GraphDatabase graphDatabaseService, IndexProvider indexProvider) {