package org.neo4j.rdf.store.representation.standard; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.meta.model.MetaModel; import org.neo4j.rdf.fulltext.FulltextIndex; import org.neo4j.rdf.model.Uri; import org.neo4j.rdf.store.representation.AbstractNode; import org.neo4j.rdf.store.representation.AbstractRelationship; import org.neo4j.rdf.store.representation.AbstractRepresentation; import org.neo4j.index.IndexService; public class VerboseQuadExecutor extends UriBasedExecutor { public static final String LITERAL_DATATYPE_KEY = START_OF_ILLEGAL_URI + "datatype"; public static final String LITERAL_LANGUAGE_KEY = START_OF_ILLEGAL_URI + "language"; public static final String STATEMENT_COUNT = "context_statement_count"; // public static final String SUBJECT_ENERGY = "subject_energy"; // public static final String OBJECT_ENERGY = "object_energy"; public static final String IS_CONTEXT_KEY = "is_context"; private static final Collection<String> EXCLUDED_LITERAL_KEYS = new HashSet<String>(); static { EXCLUDED_LITERAL_KEYS.add( LITERAL_DATATYPE_KEY ); EXCLUDED_LITERAL_KEYS.add( LITERAL_LANGUAGE_KEY ); } public static enum RelTypes implements RelationshipType { REF_CONTEXTS, IS_A_CONTEXT, } public VerboseQuadExecutor( GraphDatabaseService graphDb, IndexService index, MetaModel model, FulltextIndex fulltextIndex ) { super( graphDb, index, model, fulltextIndex ); } public Node getContextsReferenceNode() { return this.graphDbUtil().getOrCreateSubReferenceNode( RelTypes.REF_CONTEXTS ); } @Override public void addToNodeSpace( AbstractRepresentation representation ) { Map<String, AbstractNode> typeToNode = getTypeToNodeMap( representation ); if ( isLiteralRepresentation( typeToNode ) ) { handleAddLiteralRepresentation( representation, typeToNode ); } else if ( isObjectTypeRepresentation( typeToNode ) ) { handleAddObjectRepresentation( representation, typeToNode ); } else { super.addToNodeSpace( representation ); } } private void handleAddLiteralRepresentation( AbstractRepresentation representation, Map<String, AbstractNode> typeToNode ) { AbstractNode abstractSubjectNode = typeToNode.get( VerboseQuadStrategy.TYPE_SUBJECT ); AbstractNode abstractMiddleNode = typeToNode.get( VerboseQuadStrategy.TYPE_MIDDLE ); NodeContext subjectNode = lookupOrCreateNode( abstractSubjectNode, null ); AbstractRelationship subjectToMiddle = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_SUBJECT, VerboseQuadStrategy.TYPE_MIDDLE ); AbstractRelationship middleToLiteral = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_MIDDLE, VerboseQuadStrategy.TYPE_LITERAL ); AbstractNode abstractLiteralNode = typeToNode.get( VerboseQuadStrategy.TYPE_LITERAL ); Node middleNode = null; Node literalNode = null; if ( !subjectNode.wasCreated() ) { Node[] nodes = findMiddleAndObjectNode( subjectNode.getNode(), subjectToMiddle, middleToLiteral, abstractLiteralNode, null ); middleNode = nodes[ 0 ]; literalNode = nodes[ 1 ]; } boolean justAddContext = false; if ( literalNode == null ) { justAddContext = true; middleNode = createNode( abstractMiddleNode, null ); createRelationship( subjectNode.getNode(), subjectToMiddle, middleNode ); incrementSubjectEnergy( subjectNode.getNode() ); literalNode = createLiteralNode( abstractLiteralNode ); createRelationship( middleNode, middleToLiteral, literalNode ); } ensureContextsAreAdded( representation, middleNode, justAddContext ); } private Map<String, AbstractNode> getTypeToNodeMap( AbstractRepresentation representation ) { Map<String, AbstractNode> map = new HashMap<String, AbstractNode>(); for ( AbstractNode node : representation.nodes() ) { Collection<Object> nodeTypes = getNodeTypes( node ); if ( nodeTypes != null ) { for ( Object nodeType : nodeTypes ) { map.put( ( String ) nodeType, node ); } } } return map; } public String getLiteralNodePropertyKey( String predicate ) { return AbstractUriBasedExecutor.LITERAL_VALUE_KEY; } protected Node createLiteralNode( AbstractNode abstractNode ) { Node node = graphDB().createNode(); applyRepresentation( abstractNode, node ); String predicate = ( String ) abstractNode.getSingleExecutorInfo( VerboseQuadStrategy.EXECUTOR_INFO_PREDICATE ); Object value = abstractNode.properties().get( getLiteralNodePropertyKey( predicate ) ).iterator().next(); indexLiteral( node, new Uri( predicate ), value ); return node; } protected void deleteLiteralNode( Node node, String predicate, Object value ) { removeLiteralIndex( node, new Uri( predicate ), value ); deleteNode( node, null ); } private Relationship findContextRelationship( AbstractRelationship abstractRelationship, Node middleNode, boolean allowCreate, boolean justAddContext ) { AbstractNode abstractContextNode = abstractRelationship.getEndNode(); Node contextNode = lookupNode( abstractContextNode ); if ( contextNode == null && !allowCreate ) { return null; } boolean willCreateContextNode = contextNode == null; contextNode = contextNode != null ? contextNode : createNode( abstractContextNode, null ); Relationship relationship = null; if ( willCreateContextNode ) { Node contextRefNode = getContextsReferenceNode(); contextRefNode.createRelationshipTo( contextNode, RelTypes.IS_A_CONTEXT ); relationship = createRelationship( middleNode, abstractRelationship, contextNode ); incrementContextCounter( contextNode ); contextNode.setProperty( IS_CONTEXT_KEY, true ); } else { if ( !justAddContext ) { relationship = ensureDirectlyConnected( middleNode, abstractRelationship, contextNode ); } else { createRelationship( middleNode, abstractRelationship, contextNode ); } if ( relationship == null ) { // It means that it was created. incrementContextCounter( contextNode ); } // use property optimization here to avoid load of all relationships // on a heavily connected context node if ( !contextNode.hasProperty( IS_CONTEXT_KEY ) ) { if ( !contextNode.hasRelationship( RelTypes.IS_A_CONTEXT, Direction.INCOMING ) ) { Node contextRefNode = getContextsReferenceNode(); contextRefNode.createRelationshipTo( contextNode, RelTypes.IS_A_CONTEXT ); contextNode.setProperty( IS_CONTEXT_KEY, true ); } } } return relationship; } private void incrementSubjectEnergy( Node node ) { // graphDbUtil().incrementAndGetCounter( node, SUBJECT_ENERGY ); } private void decrementSubjectEnergy( Node node ) { // graphDbUtil().decrementAndGetCounter( node, SUBJECT_ENERGY, 0 ); } private void incrementObjectEnergy( Node node ) { // graphDbUtil().incrementAndGetCounter( node, OBJECT_ENERGY ); } private void decrementObjectEnergy( Node node ) { // graphDbUtil().decrementAndGetCounter( node, OBJECT_ENERGY, 0 ); } private void incrementContextCounter( Node node ) { graphDbUtil().incrementAndGetCounter( node, STATEMENT_COUNT ); } private void decrementContextCounter( Node contextNode ) { graphDbUtil().decrementAndGetCounter( contextNode, STATEMENT_COUNT, 0 ); } private void handleAddObjectRepresentation( AbstractRepresentation representation, Map<String, AbstractNode> typeToNode ) { AbstractNode abstractSubjectNode = typeToNode.get( VerboseQuadStrategy.TYPE_SUBJECT ); AbstractNode abstractMiddleNode = typeToNode.get( VerboseQuadStrategy.TYPE_MIDDLE ); AbstractNode abstractObjectNode = typeToNode.get( VerboseQuadStrategy.TYPE_OBJECT ); NodeContext subjectNode = lookupOrCreateNode( abstractSubjectNode, null ); NodeContext objectNode = lookupOrCreateNode( abstractObjectNode, null ); AbstractRelationship subjectToMiddle = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_SUBJECT, VerboseQuadStrategy.TYPE_MIDDLE ); AbstractRelationship middleToObject = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_MIDDLE, VerboseQuadStrategy.TYPE_OBJECT ); Node middleNode = null; if ( !subjectNode.wasCreated() && !objectNode.wasCreated() ) { middleNode = findMiddleAndObjectNode( subjectNode.getNode(), subjectToMiddle, middleToObject, abstractObjectNode, objectNode.getNode() )[ 0 ]; } boolean justAddContext = false; if ( middleNode == null ) { justAddContext = true; middleNode = createNode( abstractMiddleNode, null ); createRelationship( subjectNode.getNode(), subjectToMiddle, middleNode ); incrementSubjectEnergy( subjectNode.getNode() ); createRelationship( middleNode, middleToObject, objectNode.getNode() ); incrementObjectEnergy( objectNode.getNode() ); } ensureContextsAreAdded( representation, middleNode, justAddContext ); } private Node[] findMiddleAndObjectNode( Node subjectNode, AbstractRelationship subjectToMiddle, AbstractRelationship middleToObject, AbstractNode abstractObjectNode, Node objectNodeIfResource ) { Node objectNodeToLookFor = null; if ( abstractObjectNode.getUriOrNull() != null ) { objectNodeToLookFor = objectNodeIfResource; } for ( Relationship relationship : subjectNode.getRelationships( relationshipType( subjectToMiddle.getRelationshipTypeName() ), Direction.OUTGOING ) ) { Node aMiddleNode = relationship.getEndNode(); for ( Relationship rel : aMiddleNode.getRelationships( relationshipType( middleToObject.getRelationshipTypeName() ), Direction.OUTGOING ) ) { Node anObjectNode = rel.getEndNode(); if ( ( objectNodeToLookFor != null && anObjectNode.equals( objectNodeToLookFor ) ) || ( objectNodeToLookFor == null && containsProperties( anObjectNode, abstractObjectNode.properties(), EXCLUDED_LITERAL_KEYS ) ) ) { return new Node[] { aMiddleNode, anObjectNode }; } } } return new Node[] { null, null }; } private void ensureContextsAreAdded( AbstractRepresentation representation, Node middleNode, boolean justAddContext ) { for ( AbstractRelationship abstractRelationship : getContextRelationships( representation ) ) { findContextRelationship( abstractRelationship, middleNode, true, justAddContext ); } } private Collection<AbstractRelationship> getContextRelationships( AbstractRepresentation representation ) { Collection<AbstractRelationship> list = new ArrayList<AbstractRelationship>(); for ( AbstractRelationship abstractRelationship : representation.relationships() ) { String type = ( String ) abstractRelationship.getSingleExecutorInfo( VerboseQuadStrategy.EXECUTOR_INFO_NODE_TYPE ); if ( type != null && type.equals( VerboseQuadStrategy.TYPE_CONTEXT ) ) { list.add( abstractRelationship ); } } return list; } private boolean isLiteralRepresentation( Map<String, AbstractNode> typeToNode ) { return typeToNode.get( VerboseQuadStrategy.TYPE_LITERAL ) != null; } private boolean isObjectTypeRepresentation( Map<String, AbstractNode> typeToNode ) { return typeToNode.get( VerboseQuadStrategy.TYPE_OBJECT ) != null; } private Collection<Object> getNodeTypes( AbstractNode node ) { return node.getExecutorInfo( VerboseQuadStrategy.EXECUTOR_INFO_NODE_TYPE ); } private boolean nodeIsType( AbstractNode node, String type ) { Collection<Object> nodeTypes = getNodeTypes( node ); return nodeTypes != null && nodeTypes.contains( type ); } private boolean relationshipIsType( AbstractRelationship relationship, String startType, String endType ) { return nodeIsType( relationship.getStartNode(), startType ) && nodeIsType( relationship.getEndNode(), endType ); } private AbstractRelationship findAbstractRelationship( AbstractRepresentation representation, String startingType, String endingType ) { for ( AbstractRelationship relationship : representation.relationships() ) { if ( relationshipIsType( relationship, startingType, endingType ) ) { return relationship; } } return null; } @Override public void removeFromNodeSpace( AbstractRepresentation representation ) { Map<String, AbstractNode> typeToNode = getTypeToNodeMap( representation ); if ( isLiteralRepresentation( typeToNode ) ) { handleRemoveLiteralRepresentation( representation, typeToNode ); } else if ( isObjectTypeRepresentation( typeToNode ) ) { handleRemoveObjectRepresentation( representation, typeToNode ); } else { super.addToNodeSpace( representation ); } } private void handleRemoveObjectRepresentation( AbstractRepresentation representation, Map<String, AbstractNode> typeToNode ) { AbstractNode abstractSubjectNode = typeToNode.get( VerboseQuadStrategy.TYPE_SUBJECT ); Node subjectNode = lookupNode( abstractSubjectNode ); AbstractNode abstractObjectNode = typeToNode.get( VerboseQuadStrategy.TYPE_OBJECT ); Node objectNode = lookupNode( abstractObjectNode ); if ( subjectNode == null || objectNode == null ) { return; } AbstractRelationship subjectToMiddle = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_SUBJECT, VerboseQuadStrategy.TYPE_MIDDLE ); AbstractRelationship middleToObject = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_MIDDLE, VerboseQuadStrategy.TYPE_OBJECT ); Node middleNode = findMiddleAndObjectNode( subjectNode, subjectToMiddle, middleToObject, abstractObjectNode, objectNode )[ 0 ]; if ( middleNode == null ) { return; } removeContextRelationships( representation, middleNode ); if ( !middleNodeHasContexts( middleNode ) ) { disconnectMiddle( middleNode, middleToObject, objectNode, subjectNode, subjectToMiddle ); decrementSubjectEnergy( subjectNode ); decrementObjectEnergy( objectNode ); } deleteNodeIfEmpty( abstractSubjectNode, subjectNode ); // Special case where the subject and object are the same. if ( !subjectNode.equals( objectNode ) && nodeIsEmpty( abstractObjectNode, objectNode, true ) ) { deleteNode( objectNode, abstractObjectNode.getUriOrNull() ); } } static Iterable<Relationship> getExistingContextRelationships( Node middleNode ) { return middleNode.getRelationships( VerboseQuadStrategy.RelTypes.IN_CONTEXT ); } private void removeAllContextRelationships( Node middleNode ) { for ( Relationship relationship : getExistingContextRelationships( middleNode ) ) { Node contextNode = middleNode.getSingleRelationship( VerboseQuadStrategy.RelTypes.IN_CONTEXT, Direction.OUTGOING ).getEndNode(); decrementContextCounter( contextNode ); deleteRelationship( relationship ); } } private void removeSelectedContextRelationships( Node middleNode, Collection<AbstractRelationship> contextRelationships ) { for ( AbstractRelationship contextRelationship : contextRelationships ) { Node contextNode = lookupNode( contextRelationship.getEndNode() ); if ( contextNode != null ) { Relationship relationship = findDirectRelationship( middleNode, relationshipType( contextRelationship.getRelationshipTypeName() ), contextNode, Direction.OUTGOING ); if ( relationship != null ) { decrementContextCounter( contextNode ); deleteRelationship( relationship ); } } } } private void removeContextRelationships( AbstractRepresentation representation, Node middleNode ) { Collection<AbstractRelationship> contextRelationships = getContextRelationships( representation ); if ( contextRelationships.isEmpty() ) { removeAllContextRelationships( middleNode ); } else { removeSelectedContextRelationships( middleNode, contextRelationships ); } } private void handleRemoveLiteralRepresentation( AbstractRepresentation representation, Map<String, AbstractNode> typeToNode ) { AbstractNode abstractSubjectNode = typeToNode.get( VerboseQuadStrategy.TYPE_SUBJECT ); Node subjectNode = lookupNode( abstractSubjectNode ); if ( subjectNode == null ) { return; } AbstractRelationship subjectToMiddle = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_SUBJECT, VerboseQuadStrategy.TYPE_MIDDLE ); AbstractRelationship middleToLiteral = findAbstractRelationship( representation, VerboseQuadStrategy.TYPE_MIDDLE, VerboseQuadStrategy.TYPE_LITERAL ); AbstractNode abstractLiteralNode = typeToNode.get( VerboseQuadStrategy.TYPE_LITERAL ); Node[] nodes = findMiddleAndObjectNode( subjectNode, subjectToMiddle, middleToLiteral, abstractLiteralNode, null ); Node middleNode = nodes[ 0 ]; Node literalNode = nodes[ 1 ]; if ( literalNode == null ) { return; } removeContextRelationships( representation, middleNode ); if ( !middleNodeHasContexts( middleNode ) ) { deleteMiddleAndLiteral( middleNode, middleToLiteral, literalNode, subjectNode, subjectToMiddle ); decrementSubjectEnergy( subjectNode ); } deleteNodeIfEmpty( abstractSubjectNode, subjectNode ); } private boolean middleNodeHasContexts( Node middleNode ) { return getExistingContextRelationships( middleNode ).iterator().hasNext(); } // private String guessPredicateKey( Iterable<String> keys ) // { // for ( String key : keys ) // { // if ( !EXCLUDED_LITERAL_KEYS.contains( key ) ) // { // return key; // } // } // return null; // } private void deleteMiddleAndLiteral( Node middleNode, AbstractRelationship middleToLiteral, Node literalNode, Node subjectNode, AbstractRelationship subjectToMiddle ) { disconnectMiddle( middleNode, middleToLiteral, literalNode, subjectNode, subjectToMiddle ); String predicate = middleToLiteral.getRelationshipTypeName(); Object value = literalNode.getProperty( getLiteralNodePropertyKey( predicate ) ); deleteLiteralNode( literalNode, predicate, value ); } private void disconnectMiddle( Node middleNode, AbstractRelationship middleToOther, Node otherNode, Node subjectNode, AbstractRelationship subjectToMiddle ) { ensureDirectlyDisconnected( middleNode, middleToOther, otherNode ); ensureDirectlyDisconnected( subjectNode, subjectToMiddle, middleNode, Direction.INCOMING ); deleteNode( middleNode, null ); } }