DATAGRAPH-387 : More updates for LabelNodeTypeRepresentationStrategy

This commit is contained in:
Nicki Watt
2013-09-30 16:05:43 +01:00
committed by Michael Hunger
parent 400f195680
commit b40ea309ae
8 changed files with 195 additions and 95 deletions

View File

@@ -61,8 +61,10 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends EntityTestBas
private Thing thing;
private SubThing subThing;
private SubThing subSubThing;
private StoredEntityType thingType;
private StoredEntityType subThingType;
private StoredEntityType subSubThingType;
@BeforeTransaction
public void cleanDb() {
@@ -76,6 +78,7 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends EntityTestBas
}
thingType = typeOf(Thing.class);
subThingType = typeOf(SubThing.class);
subSubThingType = typeOf(SubSubThing.class);
}
@Test
@@ -95,7 +98,7 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends EntityTestBas
ClosableIterable<Node> allThings = nodeTypeRepresentationStrategy.findAll(thingType);
assertEquals("Did not find all things.",
new HashSet<PropertyContainer>(Arrays.asList(neo4jTemplate.getPersistentState(subThing), neo4jTemplate.getPersistentState(thing))),
new HashSet<PropertyContainer>(Arrays.asList(neo4jTemplate.getPersistentState(subSubThing), neo4jTemplate.getPersistentState(subThing), neo4jTemplate.getPersistentState(thing))),
IteratorUtil.addToCollection(allThings, new HashSet<Node>()));
}
@@ -104,16 +107,17 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends EntityTestBas
public void testCountOfSuperTypeIncludesSubTypes() throws Exception {
final int EXPECTED_NUM_THINGS = 1;
final int EXPECTED_NUM_SUBTHINGS = 1;
final int TOTAL_EXPECTED = EXPECTED_NUM_THINGS + EXPECTED_NUM_SUBTHINGS;
final int EXPECTED_NUM_SUBSUBTHINGS = 1;
final int TOTAL_EXPECTED = EXPECTED_NUM_THINGS + EXPECTED_NUM_SUBTHINGS + EXPECTED_NUM_SUBSUBTHINGS;
assertEquals(TOTAL_EXPECTED, nodeTypeRepresentationStrategy.count(thingType));
}
@Test
@Transactional
public void testCountOfSubTypeExcludesConcreteParents() throws Exception {
final int EXPECTED_NUM_THINGS = 1;
final int EXPECTED_NUM_SUBTHINGS = 1;
final int TOTAL_EXPECTED = EXPECTED_NUM_SUBTHINGS;
final int EXPECTED_NUM_SUBSUBTHINGS = 1;
final int TOTAL_EXPECTED = EXPECTED_NUM_SUBTHINGS + EXPECTED_NUM_SUBSUBTHINGS;
assertEquals(TOTAL_EXPECTED, nodeTypeRepresentationStrategy.count(subThingType));
}
@@ -122,8 +126,10 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends EntityTestBas
public void testGetJavaType() throws Exception {
assertEquals(thingType.getAlias(), nodeTypeRepresentationStrategy.readAliasFrom(node(thing)));
assertEquals(subThingType.getAlias(), nodeTypeRepresentationStrategy.readAliasFrom(node(subThing)));
assertEquals(subSubThingType.getAlias(), nodeTypeRepresentationStrategy.readAliasFrom(node(subSubThing)));
assertEquals(Thing.class, neo4jTemplate.getStoredJavaType(node(thing)));
assertEquals(SubThing.class, neo4jTemplate.getStoredJavaType(node(subThing)));
assertEquals(SubSubThing.class, neo4jTemplate.getStoredJavaType(node(subSubThing)));
}
@Test
@@ -162,6 +168,10 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends EntityTestBas
subThing = neo4jTemplate.setPersistentState(new SubThing(),n2);
nodeTypeRepresentationStrategy.writeTypeTo(n2, neo4jTemplate.getEntityType(SubThing.class));
subThing.setName("subThing");
Node n3 = graphDatabaseService.createNode();
subSubThing = neo4jTemplate.setPersistentState(new SubSubThing(),n3);
nodeTypeRepresentationStrategy.writeTypeTo(n3, neo4jTemplate.getEntityType(SubSubThing.class));
subThing.setName("subSubThing");
tx.success();
return thing;
} finally {
@@ -193,4 +203,7 @@ public class LabelBasedNodeTypeRepresentationStrategyTests extends EntityTestBas
public static class SubThing extends Thing {
}
public static class SubSubThing extends SubThing {
}
}

View File

@@ -1,32 +1,40 @@
package org.springframework.data.neo4j.support.mapping;
import com.tinkerpop.gremlin.Tokens;
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;
import java.util.ArrayList;
import java.util.List;
/**
* TODO - Sort out closing of Iterators properly
*
* @author Nicki Watt
* @since 24-09-2013
*/
public class ResourceIterableClosableIterable implements ClosableIterable , ResourceIterable {
private ResourceIterator iterator;
private ResourceIterable<Node> resourceIterable;
private List<ResourceIterator> requestedIterators;
public ResourceIterableClosableIterable(ResourceIterable<Node> resourceIterable) {
this.iterator = resourceIterable.iterator();
this.resourceIterable = resourceIterable;
this.requestedIterators = new ArrayList<ResourceIterator>();
}
@Override
public void close() {
iterator.close();
for (ResourceIterator ri : requestedIterators) {
ri.close();
}
}
@Override
public ResourceIterator iterator() {
return iterator;
ResourceIterator ri = resourceIterable.iterator();
requestedIterators.add(ri);
return ri;
}
};

View File

@@ -1,11 +1,12 @@
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.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author Nicki Watt
@@ -13,19 +14,39 @@ import java.util.Iterator;
*/
public class WrappedIterableClosableIterable<S> implements ClosableIterable {
private Iterator iterator;
private Iterable<S> iterable;
private boolean canBeClosed;
private List<ResourceIterator> openedResourceIterators;
public WrappedIterableClosableIterable(Iterable<S> iterable) {
this.iterator = iterable.iterator();
this.iterable = iterable;
this.canBeClosed = (iterable instanceof ResourceIterable || iterable instanceof ClosableIterable);
this.openedResourceIterators = new ArrayList<ResourceIterator>();
}
/**
* Note: Once this method has been called, it is not valid to
* call any other methods on this object thereafter;
*/
@Override
public void close() {
// no op
// Is this valid???
if (canBeClosed) {
if (iterable instanceof ClosableIterable) {
((ClosableIterable)iterable).close();
}
for (ResourceIterator ri : openedResourceIterators) {
ri.close();
}
}
openedResourceIterators = null;
iterable = null;
}
@Override
public Iterator iterator() {
return iterator;
Iterator it = iterable.iterator();
if (it instanceof ResourceIterator) {
openedResourceIterators.add((ResourceIterator)it);
}
return it;
}
};

View File

@@ -15,6 +15,7 @@
*/
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;
@@ -25,6 +26,7 @@ import org.springframework.data.neo4j.core.TypeRepresentationStrategy;
import org.springframework.data.neo4j.support.index.ClosableIndexHits;
import org.springframework.data.neo4j.support.index.IndexProvider;
import org.springframework.data.neo4j.support.index.IndexType;
import org.springframework.data.neo4j.support.index.NoSuchIndexException;
import org.springframework.data.neo4j.support.mapping.StoredEntityType;
import java.lang.Object;
@@ -49,6 +51,15 @@ public abstract class AbstractIndexBasedTypeRepresentationStrategy<S extends Pro
typesIndex = createTypesIndex();
}
public static boolean isStrategyAlreadyInUse(GraphDatabase graphDatabaseService) {
try {
final Index<PropertyContainer> index = graphDatabaseService.getIndex(IndexBasedNodeTypeRepresentationStrategy.INDEX_NAME);
return index!=null && Node.class.isAssignableFrom(index.getEntityType());
} catch(NoSuchIndexException nsie) {
return false;
}
}
private Object indexValueForType(Object alias) {
return indexProvider == null ? alias : indexProvider.createIndexValueForType(alias);
}

View File

@@ -28,10 +28,6 @@ import org.springframework.data.neo4j.support.mapping.StoredEntityType;
import org.springframework.data.neo4j.support.mapping.WrappedIterableClosableIterable;
import org.springframework.data.neo4j.support.query.QueryEngine;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Provides a Node Type Representation Strategy which makes use of Labels, and specifically
* uses Cypher as the mechanism for interacting with the graph database.
@@ -47,14 +43,15 @@ public class LabelBasedNodeTypeRepresentationStrategy implements NodeTypeReprese
protected GraphDatabase graphDb;
protected final Class<Node> clazz;
protected final LabelBasedStrategyCypherHelper cypherHelper;
protected QueryEngine<CypherQuery> queryEngine;
private boolean sdnLabelStrategyPresent;
public LabelBasedNodeTypeRepresentationStrategy(GraphDatabase graphDb) {
this.graphDb = graphDb;
this.clazz = Node.class;
this.queryEngine = graphDb.queryEngineFor(QueryType.Cypher);
this.sdnLabelStrategyPresent = false;
this.cypherHelper = new LabelBasedStrategyCypherHelper(queryEngine);
markSDNLabelStrategyInUse();
}
@Override
@@ -65,10 +62,7 @@ public class LabelBasedNodeTypeRepresentationStrategy implements NodeTypeReprese
if (state.hasLabel(sdnLabel)) {
return; // already there
}
markSDNLabelStrategyInUseIfNotExists();
addLabelsForEntityHierarchy(state,type);
}
/**
@@ -78,56 +72,45 @@ public class LabelBasedNodeTypeRepresentationStrategy implements NodeTypeReprese
* as the primary SDN marker Label.
*/
private void addLabelsForEntityHierarchy(Node state, StoredEntityType type) {
String addLabelStatement = String.format("match n where id(n)={nodeId} set n:`%s`:`%s`" , LABELSTRATEGY_PREFIX + type.getAlias(),type.getAlias());
String labels = cypherHelper.buildLabelString(LABELSTRATEGY_PREFIX + type.getAlias(), (String)type.getAlias());
labels = buildLabelStringIncludingEachEntityInHierarchy(labels, type);
cypherHelper.setLabelsOnNode(state.getId(), labels);
}
private String buildLabelStringIncludingEachEntityInHierarchy(String labels, StoredEntityType type) {
for (StoredEntityType superType : type.getSuperTypes()) {
addLabelStatement += String.format(":`%s`", superType.getAlias());
labels += cypherHelper.buildLabelString((String)superType.getAlias());
labels += buildLabelStringIncludingEachEntityInHierarchy(labels, superType);
}
Map<String,Object> params = new HashMap<String,Object>();
params.put("nodeId",state.getId());
queryEngine.query( addLabelStatement, params);
return labels;
}
/**
* Checks if a special label (SDN_LABEL_STRATEGY) exists against the reference node, and
* if it does not, it is added. This label serves as an indicator that the Labeling strategy
* has/is being used on this data set.
* Ensures that a special label (SDN_LABEL_STRATEGY) exists against the
* reference node, and if it does not, it is added. This label serves
* as an indicator that the Labeling strategy has/is going to be used on this
* data set.
*/
private void markSDNLabelStrategyInUseIfNotExists() {
if (!sdnLabelStrategyPresent) {
String query = String.format("match n where id(n)=%d and n:`%s` return count(*) ", REFERENCE_NODE_ID, SDN_LABEL_STRATEGY.name());
Long labelCount = queryEngine.query(query, Collections.EMPTY_MAP).to(Long.class).single();
if (labelCount == 0) {
String update = String.format("match n where id(n)=%d set n:`%s` ", REFERENCE_NODE_ID, SDN_LABEL_STRATEGY.name());
queryEngine.query(update, Collections.EMPTY_MAP);
}
sdnLabelStrategyPresent = true;
}
private void markSDNLabelStrategyInUse() {
cypherHelper.setLabelOnNode(REFERENCE_NODE_ID, SDN_LABEL_STRATEGY.name());
}
@Override
public <U> ClosableIterable<Node> findAll(StoredEntityType type) {
String query = String.format("match n:`%s` return n", type.getAlias().toString());
Iterable<Node> rin = queryEngine.query(query, Collections.EMPTY_MAP).to(Node.class);
Iterable<Node> rin = cypherHelper.getNodesWithLabel(type.getAlias().toString());
return new WrappedIterableClosableIterable<Node>(rin);
}
@Override
public long count(StoredEntityType type) {
String query = String.format("match n:`%s` return count(*)", type.getAlias().toString());
return queryEngine.query(query, Collections.EMPTY_MAP).to(Long.class).single();
return cypherHelper.countNodesWithLabel(type.getAlias().toString());
}
@Override
public Object readAliasFrom(Node state) {
if (state == null)
throw new IllegalArgumentException("Node is null");
String query = String.format("match n where id(n)=%d return labels(n) as labels", state.getId());
Map queryResult = queryEngine.query(query, Collections.EMPTY_MAP).to(Map.class).single();
Iterable<String> labels = (Iterable)queryResult.get("labels");
Iterable<String> labels = cypherHelper.getLabelsForNode(state.getId());
for (String label: labels) {
if (label.startsWith(LABELSTRATEGY_PREFIX)) {
return label.substring(LABELSTRATEGY_PREFIX.length());
@@ -142,4 +125,8 @@ public class LabelBasedNodeTypeRepresentationStrategy implements NodeTypeReprese
// don't think we need to do anything here!
}
public static boolean isStrategyAlreadyInUse(GraphDatabase graphDatabaseService) {
return graphDatabaseService.getReferenceNode().hasLabel(SDN_LABEL_STRATEGY);
}
}

View File

@@ -0,0 +1,82 @@
package org.springframework.data.neo4j.support.typerepresentation;
import org.neo4j.graphdb.Node;
import org.springframework.data.neo4j.repository.query.CypherQuery;
import org.springframework.data.neo4j.support.query.QueryEngine;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Provides some helper Cypher based functionality specifically
* for use by the Label Based Node Type Representation Strategy.
* This class may well become more broadly accessible / generic in time
* however for now, serves to specifically aid the
* Label Based Node Type Representation Strategy.
*
* @author Nicki Watt
* @since 24-09-2013
*/
public class LabelBasedStrategyCypherHelper {
static final String CYPHER_ADD_LABEL_TO_NODE = "match n where id(n)={nodeId} set n:`%s`";
static final String CYPHER_ADD_LABELS_TO_NODE = "match n where id(n)={nodeId} set n%s";
static final String CYPHER_COUNT_LABELS_ON_NODE = "match n where id(n)={nodeId} and n:`%s` return count(*) ";
static final String CYPHER_RETURN_NODES_WITH_LABEL = "match n:`%s` return n";
static final String CYPHER_RETURN_COUNT_OF_NODES_WITH_LABEL = "match n:`%s` return count(*)";
static final String CYPHER_RETURN_LABELS_FOR_NODE = "match n where id(n)={nodeId} return labels(n) as labels";
private QueryEngine<CypherQuery> queryEngine;
public LabelBasedStrategyCypherHelper(QueryEngine<CypherQuery> queryEngine) {
this.queryEngine = queryEngine;
}
public void setLabelOnNode(Long nodeId, String label) {
String addLabelStatement = String.format(CYPHER_ADD_LABEL_TO_NODE , label);
queryEngine.query( addLabelStatement, getParamsWithNodeId(nodeId) );
}
public void setLabelsOnNode(Long nodeId, String labelString) {
String addLabelStatement = String.format(CYPHER_ADD_LABELS_TO_NODE , labelString);
queryEngine.query( addLabelStatement, getParamsWithNodeId(nodeId) );
}
public boolean doesNodeHaveLabel(Long nodeId, String label) {
String query = String.format(CYPHER_COUNT_LABELS_ON_NODE, label);
long labelCount = queryEngine.query(query, getParamsWithNodeId(nodeId)).to(Long.class).single();
return labelCount > 0;
}
public Iterable<Node> getNodesWithLabel(String label) {
String query = String.format(CYPHER_RETURN_NODES_WITH_LABEL, label);
return queryEngine.query(query, Collections.EMPTY_MAP).to(Node.class);
}
public String buildLabelString(String... labels) {
String result = "";
for (String label: labels) {
result += ":`" + label + "`";
}
return result;
}
private Map<String, Object> getParamsWithNodeId(long id) {
Map<String,Object> params = new HashMap<String,Object>();
params.put("nodeId",id);
return params;
}
public long countNodesWithLabel(String label) {
String query = String.format(CYPHER_RETURN_COUNT_OF_NODES_WITH_LABEL, label);
return queryEngine.query(query, Collections.EMPTY_MAP).to(Long.class).single();
}
public Iterable<String> getLabelsForNode(long nodeId) {
Map queryResult = queryEngine.query(CYPHER_RETURN_LABELS_FOR_NODE, getParamsWithNodeId(nodeId)).to(Map.class).single();
return (Iterable)queryResult.get("labels");
}
}

View File

@@ -16,12 +16,7 @@
package org.springframework.data.neo4j.support.typerepresentation;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.helpers.collection.ClosableIterable;
import org.neo4j.helpers.collection.IterableWrapper;
@@ -92,6 +87,19 @@ public class SubReferenceNodeTypeRepresentationStrategy implements NodeTypeRepre
entity.removeProperty("___dummy_property_for_locking___");
}
public static boolean isStrategyAlreadyInUse(GraphDatabase graphDatabaseService) {
try {
for (Relationship rel : graphDatabaseService.getReferenceNode().getRelationships()) {
if (rel.getType().name().startsWith(SubReferenceNodeTypeRepresentationStrategy.SUBREF_PREFIX)) {
return true;
}
}
} catch(NotFoundException nfe) {
// ignore
}
return false;
}
@Override
public void writeTypeTo(Node state, StoredEntityType type) {
final Node subReference = obtainSubreferenceNode(type);

View File

@@ -30,7 +30,6 @@ public class TypeRepresentationStrategyFactory {
private final GraphDatabase graphDatabaseService;
private final Strategy strategy;
private IndexProvider indexProvider;
private QueryEngine<CypherQuery> queryEngine;
public TypeRepresentationStrategyFactory(GraphDatabase graphDatabaseService) {
this(graphDatabaseService,chooseStrategy(graphDatabaseService), null);
@@ -52,44 +51,15 @@ public class TypeRepresentationStrategyFactory {
}
private static Strategy chooseStrategy(GraphDatabase graphDatabaseService) {
Transaction tx = graphDatabaseService.beginTx();
try {
if (isAlreadyIndexed(graphDatabaseService)) return Strategy.Indexed;
if (isAlreadySubRef(graphDatabaseService)) return Strategy.SubRef;
if (isAlreadyLabeled(graphDatabaseService)) return Strategy.Labeled;
try (Transaction tx = graphDatabaseService.beginTx()) {
if (AbstractIndexBasedTypeRepresentationStrategy.isStrategyAlreadyInUse(graphDatabaseService)) return Strategy.Indexed;
if (SubReferenceNodeTypeRepresentationStrategy.isStrategyAlreadyInUse(graphDatabaseService)) return Strategy.SubRef;
if (LabelBasedNodeTypeRepresentationStrategy.isStrategyAlreadyInUse(graphDatabaseService)) return Strategy.Labeled;
tx.success();
return Strategy.Indexed;
} finally {
tx.success();tx.finish();
}
}
private static boolean isAlreadyLabeled(GraphDatabase graphDatabaseService) {
return graphDatabaseService.getReferenceNode().hasLabel(
LabelBasedNodeTypeRepresentationStrategy.SDN_LABEL_STRATEGY);
}
private static boolean isAlreadyIndexed(GraphDatabase graphDatabaseService) {
try {
final Index<PropertyContainer> index = graphDatabaseService.getIndex(IndexBasedNodeTypeRepresentationStrategy.INDEX_NAME);
return index!=null && Node.class.isAssignableFrom(index.getEntityType());
} catch(NoSuchIndexException nsie) {
return false;
}
}
private static boolean isAlreadySubRef(GraphDatabase graphDatabaseService) {
try {
for (Relationship rel : graphDatabaseService.getReferenceNode().getRelationships()) {
if (rel.getType().name().startsWith(SubReferenceNodeTypeRepresentationStrategy.SUBREF_PREFIX)) {
return true;
}
}
} catch(NotFoundException nfe) {
// ignore
}
return false;
}
public NodeTypeRepresentationStrategy getNodeTypeRepresentationStrategy() {
return strategy.getNodeTypeRepresentationStrategy(graphDatabaseService, indexProvider);
}