package org.neo4j.rdf.store.representation.standard;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphmatching.CommonValueMatchers;
import org.neo4j.graphmatching.PatternMatch;
import org.neo4j.graphmatching.PatternMatcher;
import org.neo4j.graphmatching.PatternNode;
import org.neo4j.index.IndexService;
import org.neo4j.meta.model.MetaModel;
import org.neo4j.rdf.fulltext.FulltextIndex;
import org.neo4j.rdf.model.Uri;
import org.neo4j.rdf.store.representation.AbstractElement;
import org.neo4j.rdf.store.representation.AbstractNode;
import org.neo4j.rdf.store.representation.AbstractRelationship;
import org.neo4j.rdf.store.representation.AbstractRepresentation;
import org.neo4j.rdf.store.representation.RepresentationExecutor;
import org.neo4j.util.PropertyArraySet;
/**
* An implementation of {@link RepresentationExecutor} which uses an
* {@link IndexService}, where the indexing key is each elements {@link Uri} as a way
* of looking up the objects.
*
* This is an attempt to implement a generic executor which tries to execute a
* {@link AbstractRepresentation}s as straight forward and correct as possible.
*/
public class UriBasedExecutor extends AbstractUriBasedExecutor
{
/**
* Set on an AbstractNode (with a boolean as value) to let the executor
* know that the node is a literal node (and should be indexed).
*/
public static final String EXEC_INFO_IS_LITERAL_NODE = "literal";
/**
* Set on an AbstractElement (with a Collection<String> as value) containing
* the keys in that element which should be treated as literals
* (and hence should be indexed).
*/
public static final String EXEC_INFO_KEYS_WHICH_ARE_LITERALS =
"literal_keys";
/**
* @param graphDb the {@link GraphDatabaseService}.
* @param index the {@link Index} to use as the lookup for objects.
*/
public UriBasedExecutor( GraphDatabaseService graphDb, IndexService index,
MetaModel model, FulltextIndex fulltextIndex )
{
super( graphDb, index, model, fulltextIndex );
}
public void addToNodeSpace( AbstractRepresentation representation )
{
Map<AbstractNode, Node> nodeMapping = new HashMap<AbstractNode, Node>();
for ( AbstractNode abstractNode : representation.nodes() )
{
if ( abstractNode.getUriOrNull() != null )
{
Node node = lookupOrCreateNode(
abstractNode, nodeMapping ).getNode();
applyOnNode( abstractNode, node );
}
}
for ( AbstractRelationship abstractRelationship :
representation.relationships() )
{
Node startNode = nodeMapping.get( abstractRelationship
.getStartNode() );
Node endNode = nodeMapping.get( abstractRelationship.getEndNode() );
RelationshipType relType = new ARelationshipType(
abstractRelationship.getRelationshipTypeName() );
Relationship relationship = null;
if ( startNode != null && endNode != null )
{
relationship = ensureConnected( startNode, relType, endNode );
}
else if ( startNode == null && endNode == null )
{
throw new UnsupportedOperationException(
"Both start and end null" );
}
else
{
NodeAndRelationship result = findOtherNodePresumedBlank(
abstractRelationship, representation, nodeMapping, true );
relationship = result.relationship;
if ( result.node != null )
{
applyOnNode( result.abstractNode, result.node );
}
}
if ( relationship != null )
{
applyOnRelationship( abstractRelationship, relationship );
}
}
}
public void removeFromNodeSpace( AbstractRepresentation representation )
{
Map<AbstractNode, Node> nodeMapping =
getWellKnownNodeMappings( representation );
if ( nodeMapping == null )
{
return;
}
Map<AbstractRelationship, Relationship> relationshipMapping =
new HashMap<AbstractRelationship, Relationship>();
for ( AbstractRelationship abstractRelationship :
representation.relationships() )
{
Node startNode = nodeMapping.get( abstractRelationship
.getStartNode() );
Node endNode = nodeMapping.get( abstractRelationship.getEndNode() );
RelationshipType relType = new ARelationshipType(
abstractRelationship.getRelationshipTypeName() );
if ( startNode != null && endNode != null )
{
Relationship relationship = findDirectRelationship( startNode,
relType, endNode, Direction.OUTGOING );
if ( relationship == null )
{
return;
}
relationshipMapping.put( abstractRelationship, relationship );
}
else if ( startNode == null && endNode == null )
{
throw new UnsupportedOperationException(
"Both start and end null" );
}
else
{
Node otherNode = findOtherNodePresumedBlank(
abstractRelationship, representation, nodeMapping,
false ).node;
if ( otherNode == null )
{
return;
}
Relationship relationship = startNode != null ?
findDirectRelationship( startNode, relType, otherNode,
Direction.OUTGOING )
: findDirectRelationship( otherNode, relType, startNode,
Direction.OUTGOING );
if ( relationship == null )
{
return;
}
relationshipMapping.put( abstractRelationship, relationship );
}
}
doActualDeletion( nodeMapping, relationshipMapping );
}
protected void doActualDeletion( Map<AbstractNode, Node> nodeMapping,
Map<AbstractRelationship, Relationship> relationshipMapping )
{
// Do the actual deletion
Set<Node> deletedNodes = new HashSet<Node>();
for ( Map.Entry<AbstractNode, Node> entry : nodeMapping.entrySet() )
{
AbstractNode abstractNode = entry.getKey();
Node node = entry.getValue();
removeFromNode( abstractNode, node );
if ( nodeIsEmpty( abstractNode, node, true ) )
{
deleteNode( node, abstractNode.getUriOrNull() );
deletedNodes.add( node );
}
}
for ( Map.Entry<AbstractRelationship, Relationship> entry :
relationshipMapping.entrySet() )
{
AbstractRelationship abstractRelationship = entry.getKey();
Relationship relationship = entry.getValue();
removeFromRelationship( abstractRelationship, relationship );
if ( relationshipIsEmpty( abstractRelationship, relationship ) )
{
// debugDeleteRelationship( relationship );
relationship.delete();
}
}
for ( Map.Entry<AbstractNode, Node> entry : nodeMapping.entrySet() )
{
AbstractNode abstractNode = entry.getKey();
Node node = entry.getValue();
if ( !deletedNodes.contains( node ) &&
nodeIsEmpty( abstractNode, node, true ) )
{
deleteNode( node, abstractNode.getUriOrNull() );
}
}
}
private boolean oneNodeIsBlankAndItsNotEmpty(
AbstractRelationship abstractRelationship, Relationship relationship )
{
if ( abstractRelationship.getStartNode().getUriOrNull() != null &&
abstractRelationship.getEndNode().getUriOrNull() != null )
{
return false;
}
boolean blankIsStartNode =
abstractRelationship.getStartNode().getUriOrNull() == null;
AbstractNode abstractNode = blankIsStartNode ?
abstractRelationship.getStartNode() :
abstractRelationship.getEndNode();
Node node = blankIsStartNode ?
relationship.getStartNode() : relationship.getEndNode();
if ( !nodeIsEmpty( abstractNode, node, false ) )
{
return true;
}
return false;
}
protected boolean relationshipIsEmpty(
AbstractRelationship abstractRelationship, Relationship relationship )
{
if ( oneNodeIsBlankAndItsNotEmpty(
abstractRelationship, relationship ) )
{
return false;
}
AbstractNode abstractStartNode = abstractRelationship.getStartNode();
if ( abstractStartNode.getUriOrNull() == null && !nodeIsEmpty(
abstractStartNode, relationship.getStartNode(), false ) )
{
return false;
}
boolean hasProperties =
relationship.getPropertyKeys().iterator().hasNext();
return !hasProperties;
}
private Relationship ensureConnected( Node startNode,
RelationshipType relType, Node endNode )
{
Relationship relationship =
findDirectRelationship( startNode, relType, endNode, null );
if ( relationship == null )
{
relationship = startNode.createRelationshipTo( endNode, relType );
// debug( "\t+Relationship " + startNode + " ---["
// + relType.name() + "]--> " + endNode );
}
return relationship;
}
private void applyOnRelationship( AbstractRelationship abstractRelationship,
Relationship relationship )
{
applyOnContainer( abstractRelationship, relationship );
}
protected void applyOnNode( AbstractNode abstractNode, Node node )
{
applyOnContainer( abstractNode, node );
}
private void applyOnContainer( AbstractElement abstractElement,
PropertyContainer container )
{
Collection<?> keysWhichAreLiterals = ( Collection<?> )
abstractElement.getExecutorInfo(
EXEC_INFO_KEYS_WHICH_ARE_LITERALS );
for ( Map.Entry<String, Collection<Object>> entry :
abstractElement.properties().entrySet() )
{
Collection<Object> rawValues = new PropertyArraySet<Object>(
container, entry.getKey() );
for ( Object value : entry.getValue() )
{
boolean added = rawValues.add( value );
if ( added )
{
// debug( "\t+Property" + " (" + container + ") "
// + entry.getKey() + " " + "[" + value + "]" );
if ( keysWhichAreLiterals != null &&
keysWhichAreLiterals.contains( entry.getKey() ) )
{
indexLiteral( ( Node ) container,
new Uri( entry.getKey() ), value );
}
}
}
}
}
protected void removeFromRelationship(
AbstractRelationship abstractRelationship, Relationship relationship )
{
for ( Map.Entry<String, Collection<Object>> entry :
abstractRelationship.properties().entrySet() )
{
String key = entry.getKey();
Collection<Object> rawValues = new PropertyArraySet<Object>(
relationship, key );
removeAll( relationship, key, rawValues,
entry.getValue(), "Property" );
}
}
protected boolean removeAll( PropertyContainer container, String key,
Collection<Object> rawValues, Collection<?> valuesToRemove,
String debugText )
{
boolean someRemoved = false;
for ( Object value : valuesToRemove )
{
boolean removed = rawValues.remove( value );
if ( removed )
{
someRemoved = true;
// debug( "\t-" + debugText + " (" + container + ") "
// + key + " " + "[" + value + "]" );
}
}
return someRemoved;
}
protected void removeFromNode( AbstractNode abstractNode, Node node )
{
Collection<?> keysWhichAreLiterals = ( Collection<?> )
abstractNode.getExecutorInfo( EXEC_INFO_KEYS_WHICH_ARE_LITERALS );
// Do the real keys
for ( Map.Entry<String, Collection<Object>> entry :
abstractNode.properties().entrySet() )
{
String key = entry.getKey();
Collection<Object> rawValues = new PropertyArraySet<Object>(
node, key );
for ( Object value : entry.getValue() )
{
boolean removed = rawValues.remove( value );
if ( removed )
{
if ( keysWhichAreLiterals != null &&
keysWhichAreLiterals.contains( entry.getKey() ) )
{
removeLiteralIndex( node, new Uri( key ), value );
}
// debug( "\t-Property" + " (" + node + ") "
// + key + " " + "[" + value + "]" );
}
}
}
}
private NodeAndRelationship findOtherNodePresumedBlank(
AbstractRelationship abstractRelationship,
AbstractRepresentation representation,
Map<AbstractNode, Node> nodeMapping, boolean createIfItDoesntExist )
{
Map<AbstractNode, PatternNode> patternNodes =
representationToPattern( representation );
AbstractNode startingAbstractNode = abstractRelationship.getStartNode()
.getUriOrNull() == null ? abstractRelationship.getEndNode()
: abstractRelationship.getStartNode();
AbstractNode endingAbstractNode =
abstractRelationship.getOtherNode( startingAbstractNode );
Node startingNode = nodeMapping.get( startingAbstractNode );
PatternNode startingPatternNode = patternNodes
.get( startingAbstractNode );
Iterator<PatternMatch> matches = PatternMatcher.getMatcher().match(
startingPatternNode, startingNode, null ).iterator();
Node node = null;
Relationship relationship = null;
if ( matches.hasNext() )
{
PatternMatch match = matches.next();
node = match.getNodeFor(
patternNodes.get( endingAbstractNode ) );
Node otherNode =
match.getNodeFor( patternNodes.get( startingAbstractNode ) );
relationship = findDirectRelationship( node, relationshipType(
abstractRelationship.getRelationshipTypeName() ), otherNode,
null );
}
else if ( createIfItDoesntExist )
{
NodeAndRelationship createdNodeAndRelationship =
createBlankNodeIfDoesntExist( startingAbstractNode,
endingAbstractNode, abstractRelationship, nodeMapping );
node = createdNodeAndRelationship.node;
relationship = createdNodeAndRelationship.relationship;
}
nodeMapping.put( endingAbstractNode, node );
return new NodeAndRelationship( endingAbstractNode,
node, relationship );
}
protected NodeAndRelationship createBlankNodeIfDoesntExist(
AbstractNode startNode, AbstractNode endNode,
AbstractRelationship abstractRelationship,
Map<AbstractNode, Node> nodeMapping )
{
Node node = graphDB().createNode();
Relationship relationship = null;
// debug( "\tNo match, creating node (" + node.getId()
// + ") and connecting" );
RelationshipType relationshipType = new ARelationshipType(
abstractRelationship.getRelationshipTypeName() );
if ( abstractRelationship.getStartNode().getUriOrNull() == null )
{
relationship = node.createRelationshipTo(
nodeMapping.get( abstractRelationship.getEndNode() ),
relationshipType );
// debug( "\t+Relationship " + node + " ---["
// + relationshipType.name() + "]--> "
// + nodeMapping.get( abstractRelationship.getEndNode() ) );
}
else
{
relationship = nodeMapping.get(
abstractRelationship.getStartNode() ).createRelationshipTo(
node, relationshipType );
// debug( "\t+Relationship "
// + nodeMapping.get( abstractRelationship.getStartNode() )
// + " ---[" + relationshipType.name() + "]--> " + node );
}
return new NodeAndRelationship( null, node, relationship );
}
private Map<AbstractNode, PatternNode> representationToPattern(
AbstractRepresentation representation )
{
Map<AbstractNode, PatternNode> patternNodes =
new HashMap<AbstractNode, PatternNode>();
for ( AbstractNode node : representation.nodes() )
{
PatternNode patternNode = abstractNodeToPatternNode( node );
patternNodes.put( node, patternNode );
}
for ( AbstractRelationship relationship : representation
.relationships() )
{
PatternNode startNode = patternNodes.get( relationship
.getStartNode() );
PatternNode endNode = patternNodes.get( relationship.getEndNode() );
startNode.createRelationshipTo( endNode, new ARelationshipType(
relationship.getRelationshipTypeName() ) );
}
return patternNodes;
}
private PatternNode abstractNodeToPatternNode( AbstractNode node )
{
PatternNode patternNode = new PatternNode();
if ( node.getUriOrNull() != null )
{
patternNode.addPropertyConstraint( getNodeUriPropertyKey( node ),
CommonValueMatchers.exactAny( node.getUriOrNull().getUriAsString() ) );
}
if ( isLiteralNode( node ) )
{
for ( Map.Entry<String, Collection<Object>> entry :
node.properties().entrySet() )
{
patternNode.addPropertyConstraint( entry.getKey(),
CommonValueMatchers.exactAnyOf( entry.getValue().toArray() ) );
}
}
return patternNode;
}
protected boolean isLiteralNode( AbstractNode node )
{
Boolean isLiteralNode = ( Boolean )
node.getSingleExecutorInfo( EXEC_INFO_IS_LITERAL_NODE );
return isLiteralNode != null && isLiteralNode;
}
public static class ARelationshipType implements RelationshipType
{
private String name;
public ARelationshipType( String name )
{
this.name = name;
}
public String name()
{
return this.name;
}
}
protected static class NodeAndRelationship
{
private AbstractNode abstractNode;
private Node node;
private Relationship relationship;
NodeAndRelationship( AbstractNode abstractNode,
Node node, Relationship relationship )
{
this.abstractNode = abstractNode;
this.node = node;
this.relationship = relationship;
}
}
}