/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr 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.
*
* Structr 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 Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.bolt.wrapper;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.structr.api.graph.Direction;
import org.structr.api.graph.Label;
import org.structr.api.graph.Node;
import org.structr.api.graph.Relationship;
import org.structr.api.graph.RelationshipType;
import org.structr.api.util.FixedSizeCache;
import org.structr.api.util.Iterables;
import org.structr.bolt.BoltDatabaseService;
import org.structr.bolt.SessionTransaction;
import org.structr.bolt.mapper.RelationshipRelationshipMapper;
/**
*
*/
public class NodeWrapper extends EntityWrapper<org.neo4j.driver.v1.types.Node> implements Node {
private final Map<String, Map<String, List<Relationship>>> relationshipCache = new HashMap<>();
private static FixedSizeCache<Long, NodeWrapper> nodeCache = null;
private NodeWrapper(final BoltDatabaseService db, final org.neo4j.driver.v1.types.Node node) {
super(db, node);
}
public static void initialize(final int cacheSize) {
nodeCache = new FixedSizeCache<>(cacheSize);
}
@Override
protected String getQueryPrefix() {
return "MATCH (n)";
}
@Override
public void invalidate() {
relationshipCache.clear();
this.stale = true;
}
@Override
public Relationship createRelationshipTo(final Node endNode, final RelationshipType relationshipType) {
assertNotStale();
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
final NodeWrapper otherNode = (NodeWrapper)endNode;
tx.modified(this);
tx.modified(otherNode);
assertNotStale();
map.put("id1", id);
map.put("id2", endNode.getId());
/**
* Neo4j does not seem to lock source and target node when
* creating a relationship between the two, so we need to set
* a temporary property to enforce locking on the two nodes
* for the duration of the transaction.
*/
final org.neo4j.driver.v1.types.Relationship rel = tx.getRelationship(
"MATCH (n), (m) WHERE ID(n) = {id1} AND ID(m) = {id2} "
+ "SET n.locked = true, m.locked = true "
+ "MERGE (n)-[r:" + relationshipType.name() + "]->(m) "
+ "SET n.locked = Null, m.locked = Null RETURN r",
map);
tx.modified(this);
// clear caches
((NodeWrapper)endNode).relationshipCache.clear();
relationshipCache.clear();
return RelationshipWrapper.newInstance(db, rel);
}
@Override
public void addLabel(final Label label) {
assertNotStale();
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
map.put("id", id);
tx.set("MATCH (n) WHERE ID(n) = {id} SET n :" + label.name(), map);
tx.modified(this);
}
@Override
public void removeLabel(final Label label) {
assertNotStale();
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
map.put("id", id);
tx.set("MATCH (n) WHERE ID(n) = {id} REMOVE n:" + label.name(), map);
tx.modified(this);
}
@Override
public Iterable<Label> getLabels() {
assertNotStale();
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
final List<Label> result = new LinkedList<>();
map.put("id", id);
// execute query
for (final String label : tx.getStrings("MATCH (n) WHERE ID(n) = {id} RETURN LABELS(n)", map)) {
result.add(db.forName(Label.class, label));
}
return result;
}
@Override
public Iterable<Relationship> getRelationships() {
assertNotStale();
final RelationshipRelationshipMapper mapper = new RelationshipRelationshipMapper(db);
List<Relationship> list = getList(null, null);
if (list == null) {
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
map.put("id", id);
list = Iterables.toList(Iterables.map(mapper, tx.getRelationships("MATCH (n)-[r]-() WHERE ID(n) = {id} RETURN r", map)));
// store in cache
setList(null, null, list);
}
return list;
}
@Override
public Iterable<Relationship> getRelationships(final Direction direction) {
assertNotStale();
final RelationshipRelationshipMapper mapper = new RelationshipRelationshipMapper(db);
List<Relationship> list = getList(direction, null);
if (list == null) {
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
map.put("id", id);
switch (direction) {
case BOTH:
return getRelationships();
case OUTGOING:
list = Iterables.toList(Iterables.map(mapper, tx.getRelationships("MATCH (n)-[r]->() WHERE ID(n) = {id} RETURN r", map)));
break;
case INCOMING:
list = Iterables.toList(Iterables.map(mapper, tx.getRelationships("MATCH (n)<-[r]-() WHERE ID(n) = {id} RETURN r", map)));
break;
}
setList(direction, null, list);
}
return list;
}
@Override
public Iterable<Relationship> getRelationships(final Direction direction, final RelationshipType relationshipType) {
assertNotStale();
final RelationshipRelationshipMapper mapper = new RelationshipRelationshipMapper(db);
List<Relationship> list = getList(direction, relationshipType);
if (list == null) {
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
map.put("id", id);
switch (direction) {
case BOTH:
list = Iterables.toList(Iterables.map(mapper, tx.getRelationships("MATCH (n)-[r:" + relationshipType.name() + "]-() WHERE ID(n) = {id} RETURN r", map)));
break;
case OUTGOING:
list = Iterables.toList(Iterables.map(mapper, tx.getRelationships("MATCH (n)-[r:" + relationshipType.name() + "]->() WHERE ID(n) = {id} RETURN r", map)));
break;
case INCOMING:
list = Iterables.toList(Iterables.map(mapper, tx.getRelationships("MATCH (n)<-[r:" + relationshipType.name() + "]-() WHERE ID(n) = {id} RETURN r", map)));
break;
}
setList(direction, relationshipType, list);
}
return list;
}
@Override
public void delete() {
super.delete();
nodeCache.remove(id);
}
public static void clearCache() {
nodeCache.clear();
}
// ----- public static methods -----
public static NodeWrapper newInstance(final BoltDatabaseService db, final org.neo4j.driver.v1.types.Node node) {
synchronized (nodeCache) {
NodeWrapper wrapper = nodeCache.get(node.id());
if (wrapper == null) {
wrapper = new NodeWrapper(db, node);
nodeCache.put(node.id(), wrapper);
}
return wrapper;
}
}
public static NodeWrapper newInstance(final BoltDatabaseService db, final long id) {
synchronized (nodeCache) {
NodeWrapper wrapper = nodeCache.get(id);
if (wrapper == null) {
final SessionTransaction tx = db.getCurrentTransaction();
final Map<String, Object> map = new HashMap<>();
map.put("id", id);
wrapper = new NodeWrapper(db, tx.getNode("MATCH (n) WHERE ID(n) = {id} RETURN n", map));
nodeCache.put(id, wrapper);
}
return wrapper;
}
}
// ----- private methods -----
private Map<String, List<Relationship>> getCache(final Direction direction) {
final String key = direction != null ? direction.name() : "*";
Map<String, List<Relationship>> cache = relationshipCache.get(key);
if (cache == null) {
cache = new HashMap<>();
relationshipCache.put(key, cache);
}
return cache;
}
private List<Relationship> getList(final Direction direction, final RelationshipType relType) {
final String key = relType != null ? relType.name() : "*";
final Map<String, List<Relationship>> cache = getCache(direction);
return cache.get(key);
}
private void setList(final Direction direction, final RelationshipType relType, final List<Relationship> list) {
final String key = relType != null ? relType.name() : "*";
final Map<String, List<Relationship>> cache = getCache(direction);
cache.put(key, list);
}
}