/**
* This file is part of d:swarm graph extension.
*
* d:swarm graph extension is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* d:swarm graph extension is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with d:swarm graph extension. If not, see <http://www.gnu.org/licenses/>.
*/
package org.dswarm.graph.gdm.work;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.dswarm.graph.DMPGraphException;
import org.dswarm.graph.NodeType;
import org.dswarm.graph.delta.DeltaState;
import org.dswarm.graph.delta.DeltaStatics;
import org.dswarm.graph.delta.util.GraphDBUtil;
import org.dswarm.graph.index.NamespaceIndex;
import org.dswarm.graph.json.LiteralNode;
import org.dswarm.graph.json.Predicate;
import org.dswarm.graph.json.ResourceNode;
import org.dswarm.graph.json.Statement;
import org.dswarm.graph.model.GraphStatics;
import org.dswarm.graph.read.NodeHandler;
import org.dswarm.graph.utils.GraphUtils;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Iterables;
/**
* @author tgaengler
*/
public class PropertyGraphDeltaGDMSubGraphWorker implements GDMSubGraphWorker {
private static final Logger LOG = LoggerFactory.getLogger(PropertyGraphDeltaGDMSubGraphWorker.class);
private final SubGraphNodeHandler nodeHandler;
private final NodeHandler startNodeHandler;
private final SubGraphRelationshipHandler relationshipHandler;
private final String prefixedResourceURI;
private final DeltaState deltaState;
private final GraphDatabaseService database;
private final NamespaceIndex namespaceIndex;
private final Map<Long, Statement> currentSubGraphs = new LinkedHashMap<>();
private final List<Path> subGraphPaths = new ArrayList<>();
final Map<Long, org.dswarm.graph.json.Node> bnodes = new HashMap<>();
final Map<String, ResourceNode> resourceNodes = new HashMap<>();
final Map<String, Predicate> predicates = new HashMap<>();
public PropertyGraphDeltaGDMSubGraphWorker(final String prefixedResourceURIArg, final DeltaState deltaStateArg,
final GraphDatabaseService databaseArg, final NamespaceIndex namespaceIndexArg) {
prefixedResourceURI = prefixedResourceURIArg;
deltaState = deltaStateArg;
database = databaseArg;
namespaceIndex = namespaceIndexArg;
nodeHandler = new CBDNodeHandler();
startNodeHandler = new CBDStartNodeHandler();
relationshipHandler = new CBDRelationshipHandler();
}
@Override
public Map<Long, Statement> work() throws DMPGraphException {
try (final Transaction tx = database.beginTx()) {
PropertyGraphDeltaGDMSubGraphWorker.LOG.debug("start delta GDM TX");
final Node recordNode = GraphDBUtil.getResourceNode(database, prefixedResourceURI);
if (recordNode == null) {
PropertyGraphDeltaGDMSubGraphWorker.LOG.debug("couldn't find record for resource '{}'", prefixedResourceURI);
tx.success();
PropertyGraphDeltaGDMSubGraphWorker.LOG.debug("finished delta GDM TX successfully");
return null;
}
startNodeHandler.handleNode(recordNode);
// GraphDBUtil.printPaths(subGraphPaths);
// convert paths to statement collections
for (final Path subGraphPath : subGraphPaths) {
Long stmtIdentifier = null;
if (deltaState.equals(DeltaState.MODIFICATION)) {
stmtIdentifier = subGraphPath.endNode().getId();
}
for (final Relationship rel : subGraphPath.relationships()) {
if (!deltaState.equals(DeltaState.MODIFICATION)) {
stmtIdentifier = (Long) rel.getProperty(GraphStatics.UUID_PROPERTY, null);
}
if (currentSubGraphs.containsKey(stmtIdentifier)) {
continue;
}
final org.dswarm.graph.json.Node subject = getNode(rel.getStartNode());
final String prefixedPredicateURI = rel.getType().name();
final String fullPredicateURI = namespaceIndex.createFullURI(prefixedPredicateURI);
final Predicate predicate = getPredicate(fullPredicateURI);
final org.dswarm.graph.json.Node object = getNode(rel.getEndNode());
final Long order = (Long) rel.getProperty(GraphStatics.ORDER_PROPERTY, null);
final Long uuid = (Long) rel.getProperty(GraphStatics.UUID_PROPERTY, null);
final Statement statement = new Statement(subject, predicate, object);
if (order != null) {
statement.setOrder(order);
}
if (uuid != null) {
statement.setUUID(uuid.toString());
}
currentSubGraphs.put(stmtIdentifier, statement);
}
}
tx.success();
PropertyGraphDeltaGDMSubGraphWorker.LOG.debug("finished delta GDM TX successfully");
} catch (final Exception e) {
final String message = "couldn't finished delta GDM TX successfully";
PropertyGraphDeltaGDMSubGraphWorker.LOG.error(message, e);
throw new DMPGraphException(message);
}
return currentSubGraphs;
}
private Predicate getPredicate(final String predicateName) {
if(!predicates.containsKey(predicateName)) {
predicates.put(predicateName, new Predicate(predicateName));
}
return predicates.get(predicateName);
}
private org.dswarm.graph.json.Node getNode(final Node node) throws DMPGraphException {
final NodeType nodeType = GraphUtils.determineNodeType(node);
final org.dswarm.graph.json.Node gdmNode;
final long id = node.getId();
switch(nodeType) {
case Resource:
case TypeResource:
final String prefixedURI = (String) node.getProperty(GraphStatics.URI_PROPERTY, null);
gdmNode = createResourceFromURI(id, prefixedURI);
break;
case BNode:
case TypeBNode:
gdmNode = createResourceFromBNode(id);
break;
case Literal:
final String value = (String) node.getProperty(GraphStatics.VALUE_PROPERTY, null);
gdmNode = new LiteralNode(id, value);
break;
default:
gdmNode = null;
}
return gdmNode;
}
private org.dswarm.graph.json.Node createResourceFromBNode(final long bnodeId) {
if (!bnodes.containsKey(bnodeId)) {
bnodes.put(bnodeId, new org.dswarm.graph.json.Node(bnodeId));
}
return bnodes.get(bnodeId);
}
private ResourceNode createResourceFromURI(final long id, final String prefixedURI) throws DMPGraphException {
if (!resourceNodes.containsKey(prefixedURI)) {
final String fullURI = namespaceIndex.createFullURI(prefixedURI);
resourceNodes.put(prefixedURI, new ResourceNode(id, fullURI));
}
return resourceNodes.get(prefixedURI);
}
private class CBDNodeHandler implements SubGraphNodeHandler {
@Override
public void handleNode(final Node node, final Path path) throws DMPGraphException {
final Iterable<Relationship> relationships = node.getRelationships(Direction.OUTGOING);
// copy path, if multiple relationships available for the node
final Map<Relationship, Path> relPaths = new LinkedHashMap<>();
int i = 0;
for (final Relationship relationship : relationships) {
final Path currentPath;
if(path == null) {
currentPath = null;
}
else if(i > 0) {
try {
currentPath = (Path) ((DeltaPath) path).clone();
} catch (final CloneNotSupportedException e) {
final String message = "couldn't duplicate working path";
LOG.error(message);
throw new DMPGraphException(message);
}
} else {
currentPath = path;
}
relPaths.put(relationship, currentPath);
i++;
}
for(final Map.Entry<Relationship, Path> relPathEntry : relPaths.entrySet()) {
relationshipHandler.handleRelationship(relPathEntry.getKey(), relPathEntry.getValue());
}
if(i == 0 && path != null) {
subGraphPaths.add(path);
}
}
}
private class CBDStartNodeHandler implements NodeHandler {
@Override
public void handleNode(final Node node) throws DMPGraphException {
final Iterable<Relationship> relationships = node.getRelationships(Direction.OUTGOING);
for (final Relationship relationship : relationships) {
relationshipHandler.handleRelationship(relationship, null);
}
}
}
private class CBDRelationshipHandler implements SubGraphRelationshipHandler {
@Override
public void handleRelationship(final Relationship rel, final Path path) throws DMPGraphException {
final String deltaStateString = (String) rel.getProperty(DeltaStatics.DELTA_STATE_PROPERTY, null);
if (deltaStateString == null) {
throw new DMPGraphException(String.format("statement %d should have a delta state", rel.getId()));
}
final DeltaState currentDeltaState = DeltaState.getByName(deltaStateString);
if(!deltaState.equals(currentDeltaState)) {
nodeHandler.handleNode(rel.getEndNode(), null);
return;
}
final Path currentPath;
if(path != null) {
currentPath = path;
((DeltaPath) currentPath).addRelationship(rel);
} else {
currentPath = new DeltaPath(rel);
}
nodeHandler.handleNode(rel.getEndNode(), currentPath);
}
}
private class DeltaPath implements Path, Cloneable {
private final Node startNode;
private final Map<Long, Relationship> rels;
public DeltaPath(final Relationship startRel) {
rels = new LinkedHashMap<>();
startNode = startRel.getStartNode();
addRelationship(startRel);
}
public DeltaPath(final Map<Long, Relationship> relsArg) {
final Relationship first = Iterables.getFirst(relsArg.values(), null);
startNode = first.getStartNode();
rels = relsArg;
}
public void addRelationship(final Relationship rel) {
rels.put(rel.getId(), rel);
}
@Override
public Node startNode() {
return startNode;
}
@Override
public Node endNode() {
return Iterables.getLast(rels.values()).getEndNode();
}
@Override
public Relationship lastRelationship() {
return Iterables.getLast(rels.values());
}
@Override
public Iterable<Relationship> relationships() {
return rels.values();
}
@Override
public Iterable<Relationship> reverseRelationships() {
return null;
}
@Override
public Iterable<Node> nodes() {
final Set<Node> nodes = new LinkedHashSet<>();
for(final Relationship rel : rels.values()) {
nodes.add(rel.getStartNode());
nodes.add(rel.getEndNode());
}
return nodes;
}
@Override
public Iterable<Node> reverseNodes() {
return null;
}
@Override
public int length() {
return rels.size();
}
@Override
public Iterator<PropertyContainer> iterator() {
return null;
}
@Override protected Object clone() throws CloneNotSupportedException {
final Map<Long, Relationship> newRels = new LinkedHashMap<>();
for(final Map.Entry<Long, Relationship> relEntry : rels.entrySet()) {
newRels.put(relEntry.getKey(), relEntry.getValue());
}
return new DeltaPath(newRels);
}
}
}