/************************************************************************ * Copyright (c) 2014-2016 IoT-Solutions e.U. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ************************************************************************/ package iot.jcypher.database.embedded; import iot.jcypher.database.internal.DBUtil; import iot.jcypher.database.internal.IDBAccessInit; import iot.jcypher.database.util.QParamsUtil; import iot.jcypher.query.JcQuery; import iot.jcypher.query.JcQueryResult; import iot.jcypher.query.result.JcError; import iot.jcypher.query.writer.CypherWriter; import iot.jcypher.query.writer.IQueryParam; import iot.jcypher.query.writer.QueryParam; import iot.jcypher.query.writer.QueryParamSet; import iot.jcypher.query.writer.WriterContext; import iot.jcypher.transaction.ITransaction; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import javax.json.Json; import javax.json.JsonArrayBuilder; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; import org.neo4j.driver.v1.AuthToken; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Label; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Path; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Result; import org.neo4j.graphdb.Transaction; import scala.collection.convert.Wrappers.SeqWrapper; public abstract class AbstractEmbeddedDBAccess implements IDBAccessInit { private ThreadLocal<ETransactionImpl> transaction = new ThreadLocal<ETransactionImpl>(); protected Properties properties; private GraphDatabaseService graphDb; private Thread shutdownHook; @Override public JcQueryResult execute(JcQuery query) { List<JcQuery> qList = new ArrayList<JcQuery>(); qList.add(query); List<JcQueryResult> qrList = execute(qList); return qrList.get(0); } @Override public List<JcQueryResult> execute(List<JcQuery> queries) { List<Statement> statements = new ArrayList<Statement>(queries.size()); for (JcQuery query : queries) { WriterContext context = new WriterContext(); QueryParam.setExtractParams(query.isExtractParams(), context); CypherWriter.toCypherExpression(query, context); String cypher = context.buffer.toString(); Map<String, Object> paramsMap = QParamsUtil.createQueryParams(context); statements.add(new Statement(cypher, paramsMap)); } JsonBuilderContext builderContext = new JsonBuilderContext(); initJsonBuilderContext(builderContext); Throwable exception = null; GraphDatabaseService engine = null; try { engine = getGraphDB(); } catch(Throwable e) { exception = e; } Result result = null; Transaction tx = null; Throwable dbException = null; if (engine != null) { ETransactionImpl etx = null; if ((etx = this.transaction.get()) != null) tx = etx.getTransaction(); else tx = getGraphDB().beginTx(); try { for (Statement statement : statements) { if (statement.parameterMap != null) result = engine.execute(statement.cypher, statement.parameterMap); else result = engine.execute(statement.cypher); addInnerResultsAndDataArray(builderContext); List<String> cols = result.columns(); addColumns(builderContext, cols); while(result.hasNext()) { // that is one row Map<String, Object> row = result.next(); addRow(builderContext, row, cols); } } if (etx == null) tx.success(); } catch (Throwable e) { dbException = e; if (tx != null) tx.failure(); } finally { if (etx == null && tx != null) { try { tx.close(); } catch(Throwable e1) { dbException = e1; } } } } if (dbException != null) { addDBError(builderContext, dbException); } JsonObject jsonResult = builderContext.build(); List<JcQueryResult> ret = new ArrayList<JcQueryResult>(queries.size()); for (int i = 0; i < queries.size(); i++) { JcQueryResult qr = new JcQueryResult(jsonResult, i, this); ret.add(qr); if (exception != null) { String typ = exception.getClass().getSimpleName(); String msg = exception.getLocalizedMessage(); qr.addGeneralError(new JcError(typ, msg, DBUtil.getStacktrace(exception))); } } return ret; } @Override public List<JcError> clearDatabase() { return DBUtil.clearDatabase(this); } @Override public ITransaction beginTX() { ETransactionImpl tx = this.transaction.get(); if (tx == null) { tx = new ETransactionImpl(this); this.transaction.set(tx); } return tx; } @Override public ITransaction getTX() { return this.transaction.get(); } @Override public boolean isDatabaseEmpty() { return DBUtil.isDatabaseEmpty(this); } @Override public synchronized void close() { if (this.shutdownHook != null) { Runtime.getRuntime().removeShutdownHook(this.shutdownHook); this.shutdownHook = null; } if (this.graphDb != null) { try { this.graphDb.shutdown(); } catch (Throwable e) { // do nothing } this.graphDb = null; } } @Override public void setAuth(String userId, String password) { // nop } @Override public void setAuth(AuthToken authToken) { // nop } private void addDBError(JsonBuilderContext builderContext, Throwable exception) { String code = exception.getClass().getSimpleName(); String msg = exception.getLocalizedMessage(); JsonObjectBuilder errorObject = Json.createObjectBuilder(); errorObject.add("code", code); if (msg == null) msg = code; errorObject.add("message", msg); errorObject.add("info", DBUtil.getStacktrace(exception)); builderContext.errorsArray.add(errorObject); } protected abstract GraphDatabaseService createGraphDB(); public synchronized GraphDatabaseService getGraphDB() { if (this.graphDb == null) { this.graphDb = createGraphDB(); this.shutdownHook = registerShutdownHook(this.graphDb); } return this.graphDb; } private void initJsonBuilderContext(JsonBuilderContext builderContext) { builderContext.resultObject = Json.createObjectBuilder(); builderContext.resultsArray = Json.createArrayBuilder(); builderContext.errorsArray = Json.createArrayBuilder(); builderContext.innerResultsObjects = new ArrayList<JsonObjectBuilder>(); builderContext.dataArrays = new ArrayList<JsonArrayBuilder>(); } private void addInnerResultsAndDataArray(JsonBuilderContext builderContext) { builderContext.innerResultsObjects.add(Json.createObjectBuilder()); builderContext.dataArrays.add(Json.createArrayBuilder()); } private void addColumns(JsonBuilderContext builderContext, List<String> cols) { JsonArrayBuilder columns = Json.createArrayBuilder(); for (String col : cols) { columns.add(col); } builderContext.innerResultsObjects.get( builderContext.innerResultsObjects.size() - 1).add("columns", columns); } private void addRow(JsonBuilderContext builderContext, Map<String, Object> row, List<String> cols) { JsonObjectBuilder rowObject = Json.createObjectBuilder(); JsonArrayBuilder restArray = Json.createArrayBuilder(); JsonObjectBuilder graphObject = Json.createObjectBuilder(); JsonArrayBuilder nodesArray = Json.createArrayBuilder(); JsonArrayBuilder relationsArray = Json.createArrayBuilder(); Object restObject; List<NodeHolder> nodes = new ArrayList<NodeHolder>(); List<RelationHolder> relations = new ArrayList<RelationHolder>(); for (int i = 0; i < cols.size(); i++) { restObject = null; String key = cols.get(i); Object val = row.get(key); restObject = buildRestObject(val, nodes, relations); if (restObject == null) writeLiteralValue(val, restArray); else if (restObject instanceof JsonObjectBuilder) restArray.add((JsonObjectBuilder)restObject); else if (restObject instanceof JsonArrayBuilder) restArray.add((JsonArrayBuilder)restObject); } Collections.sort(nodes); Collections.sort(relations); for (NodeHolder nh : nodes) { nodesArray.add(nh.nodeObject); } for (RelationHolder rh : relations) { relationsArray.add(rh.relationObject); } rowObject.add("rest", restArray); graphObject.add("nodes", nodesArray); graphObject.add("relationships", relationsArray); rowObject.add("graph", graphObject); builderContext.dataArrays.get( builderContext.dataArrays.size() - 1).add(rowObject); } private Object buildRestObject(Object val, List<NodeHolder> nodes, List<RelationHolder> relations) { JsonObjectBuilder restObject = null; JsonArrayBuilder jsarr = null; if (val instanceof Node) { restObject = Json.createObjectBuilder(); Node node = (Node)val; restObject.add("self", "node/".concat(String.valueOf(node.getId()))); addNode(node, nodes); } else if (val instanceof Relationship) { restObject = Json.createObjectBuilder(); Relationship relation = (Relationship)val; restObject.add("start", "node/".concat(String.valueOf(relation.getStartNode().getId()))); restObject.add("self", "relationship/".concat(String.valueOf(relation.getId()))); restObject.add("end", "node/".concat(String.valueOf(relation.getEndNode().getId()))); RelationHolder rh = new RelationHolder(relation); if (!relations.contains(rh)) { RelationHolder.RelationNodes nds = rh.init(relation); addNode(nds.startNode, nodes); addNode(nds.endNode, nodes); relations.add(rh); } } else if (val instanceof Path) { restObject = Json.createObjectBuilder(); Path path = (Path)val; restObject.add("start", "node/".concat(String.valueOf(path.startNode().getId()))); JsonArrayBuilder pNodesArray = Json.createArrayBuilder(); Iterator<Node> nit = path.nodes().iterator(); while (nit.hasNext()) { Node node = nit.next(); pNodesArray.add("node/".concat(String.valueOf(node.getId()))); addNode(node, nodes); } restObject.add("nodes", pNodesArray); restObject.add("length", path.length()); JsonArrayBuilder pRelationsArray = Json.createArrayBuilder(); Iterator<Relationship> rit = path.relationships().iterator(); while (rit.hasNext()) { Relationship relation = rit.next(); pRelationsArray.add("relationship/".concat(String.valueOf(relation.getId()))); RelationHolder rh = new RelationHolder(relation); if (!relations.contains(rh)) { rh.init(relation); // nodes have already been added for the path relations.add(rh); } } restObject.add("relationships", pRelationsArray); restObject.add("end", "node/".concat(String.valueOf(path.endNode().getId()))); } else if (val instanceof SeqWrapper<?>) { jsarr = Json.createArrayBuilder(); int sz = ((SeqWrapper<?>)val).size(); boolean restFound = false; for (int i = 0; i < sz; i++) { Object obj = ((SeqWrapper<?>)val).get(i); Object ro = buildRestObject(obj, nodes, relations); if (ro instanceof JsonObjectBuilder) { jsarr.add((JsonObjectBuilder)ro); restFound = true; } else if (ro instanceof JsonArrayBuilder) { jsarr.add((JsonArrayBuilder)ro); restFound = true; } } if (!restFound) jsarr = null; } if (restObject != null) return restObject; if (jsarr != null) return jsarr; return null; } private static void writeLiteralValue(Object val, JsonArrayBuilder array) { if (val == null) array.addNull(); else if (val instanceof String) array.add(val.toString()); else if (val instanceof Number) { if (val instanceof Long) array.add(((Long)val).longValue()); else if (val instanceof Integer) array.add(((Integer)val).intValue()); else if (val instanceof Double) array.add(((Double)val).doubleValue()); else if (val instanceof Float) array.add(((Float)val).floatValue()); } else if (val instanceof Boolean) { array.add(((Boolean)val).booleanValue()); } else if (val != null && val.getClass().isArray()) { JsonArrayBuilder jsarr = Json.createArrayBuilder(); if (val instanceof int[]) { int[] arr = (int[])val; for (int v : arr) { writeLiteralValue(v, jsarr); } } else if (val instanceof long[]) { long[] arr = (long[])val; for (long v : arr) { writeLiteralValue(v, jsarr); } } else if (val instanceof float[]) { float[] arr = (float[])val; for (float v : arr) { writeLiteralValue(v, jsarr); } } else if (val instanceof double[]) { double[] arr = (double[])val; for (double v : arr) { writeLiteralValue(v, jsarr); } } else { Object[] arr = (Object[])val; for (Object v : arr) { writeLiteralValue(v, jsarr); } } array.add(jsarr); } else if (val instanceof SeqWrapper<?>) { JsonArrayBuilder jsarr = Json.createArrayBuilder(); int sz = ((SeqWrapper<?>)val).size(); for (int i = 0; i < sz; i++) { Object obj = ((SeqWrapper<?>)val).get(i); writeLiteralValue(obj, jsarr); } array.add(jsarr); } } private void addNode(Node node, List<NodeHolder> nodes) { if (node != null) { NodeHolder nh = new NodeHolder(node); if (!nodes.contains(nh)) { nh.init(node); nodes.add(nh); } } } void removeTx() { this.transaction.remove(); } private static Thread registerShutdownHook(final GraphDatabaseService gDb) { // Registers a shutdown hook for the Neo4j instance so that it // shuts down nicely when the VM exits (even if you "Ctrl-C" the // running application). Thread hook = new Thread() { @Override public void run() { try { gDb.shutdown(); } catch (Throwable e) { // do nothing } } }; Runtime.getRuntime().addShutdownHook(hook); return hook; } /*************************************************/ private static class JsonBuilderContext { private JsonObjectBuilder resultObject; private JsonArrayBuilder resultsArray; private JsonArrayBuilder errorsArray; private List<JsonObjectBuilder> innerResultsObjects; private List<JsonArrayBuilder> dataArrays; private JsonObject build() { for (int i = 0; i < this.innerResultsObjects.size(); i++) { this.innerResultsObjects.get(i).add("data", this.dataArrays.get(i)); this.resultsArray.add(this.innerResultsObjects.get(i)); } this.resultObject.add("results", this.resultsArray); this.resultObject.add("errors", this.errorsArray); return this.resultObject.build(); } } /*************************************************/ private static class NodeHolder implements Comparable<NodeHolder> { private long id; private JsonObjectBuilder nodeObject; private NodeHolder(Node node) { super(); this.id = node.getId(); } private void init(Node node) { JsonObjectBuilder nd = Json.createObjectBuilder(); nd.add("id", String.valueOf(node.getId())); JsonArrayBuilder labels = Json.createArrayBuilder(); Iterator<Label> lblIter = node.getLabels().iterator(); boolean hasLabels = false; while (lblIter.hasNext()) { hasLabels = true; Label lab = lblIter.next(); labels.add(lab.name()); } if (hasLabels) nd.add("labels", labels); JsonObjectBuilder props = Json.createObjectBuilder(); Iterator<String> pit = node.getPropertyKeys().iterator(); while (pit.hasNext()) { String pKey = pit.next(); Object pval = node.getProperty(pKey); writeLiteral(pKey, pval, props); } nd.add("properties", props); this.nodeObject = nd; } private static void writeLiteral(String key, Object val, JsonObjectBuilder props) { if (val instanceof String) props.add(key, val.toString()); else if (val instanceof Number) { if (val instanceof Long) props.add(key, ((Long)val).longValue()); else if (val instanceof Integer) props.add(key, ((Integer)val).intValue()); else if (val instanceof Double) props.add(key, ((Double)val).doubleValue()); else if (val instanceof Float) props.add(key, ((Float)val).floatValue()); } else if (val instanceof Boolean) { props.add(key, ((Boolean)val).booleanValue()); } else if (val != null && val.getClass().isArray()) { JsonArrayBuilder jsarr = Json.createArrayBuilder(); if (val instanceof int[]) { int[] arr = (int[])val; for (int v : arr) { writeLiteralValue(v, jsarr); } } else if (val instanceof long[]) { long[] arr = (long[])val; for (long v : arr) { writeLiteralValue(v, jsarr); } } else if (val instanceof float[]) { float[] arr = (float[])val; for (float v : arr) { writeLiteralValue(v, jsarr); } } else if (val instanceof double[]) { double[] arr = (double[])val; for (double v : arr) { writeLiteralValue(v, jsarr); } } else { Object[] arr = (Object[])val; for (Object v : arr) { writeLiteralValue(v, jsarr); } } props.add(key, jsarr); } } @Override public int hashCode() { return (int) this.id; } @Override public boolean equals(Object obj) { if (obj instanceof NodeHolder) return ((NodeHolder)obj).id == this.id; return false; } @Override public int compareTo(NodeHolder o) { return Long.compare(this.id, o.id); } } /*************************************************/ private static class RelationHolder implements Comparable<RelationHolder> { private long id; private JsonObjectBuilder relationObject; private RelationHolder(Relationship relation) { super(); this.id = relation.getId(); } private RelationNodes init(Relationship relation) { RelationNodes ret = new RelationNodes(); JsonObjectBuilder rel = Json.createObjectBuilder(); rel.add("id", String.valueOf(relation.getId())); RelationshipType typ = relation.getType(); if (typ != null) rel.add("type", typ.name()); Node sn = relation.getStartNode(); if (sn != null) { rel.add("startNode", String.valueOf(sn.getId())); ret.startNode = sn; } Node en = relation.getEndNode(); if (en != null) { rel.add("endNode", String.valueOf(en.getId())); ret.endNode = en; } JsonObjectBuilder props = Json.createObjectBuilder(); Iterator<String> pit = relation.getPropertyKeys().iterator(); while (pit.hasNext()) { String pKey = pit.next(); Object pval = relation.getProperty(pKey); writeLiteral(pKey, pval, props); } rel.add("properties", props); this.relationObject = rel; return ret; } private void writeLiteral(String key, Object val, JsonObjectBuilder props) { if (val instanceof String) props.add(key, val.toString()); else if (val instanceof Number) { if (val instanceof Long) props.add(key, ((Long)val).longValue()); else if (val instanceof Integer) props.add(key, ((Integer)val).intValue()); else if (val instanceof Double) props.add(key, ((Double)val).doubleValue()); else if (val instanceof Float) props.add(key, ((Float)val).floatValue()); } else if (val instanceof Boolean) props.add(key, ((Boolean)val).booleanValue()); } @Override public int hashCode() { return (int) this.id; } @Override public boolean equals(Object obj) { if (obj instanceof RelationHolder) return ((RelationHolder)obj).id == this.id; return false; } @Override public int compareTo(RelationHolder o) { return Long.compare(this.id, o.id); } /*****************************************/ private static class RelationNodes { private Node startNode; private Node endNode; } } /*******************************************/ private static class Statement { private String cypher; private Map<String, Object> parameterMap; private Statement(String cypher, Map<String, Object> parameterMap) { super(); this.cypher = cypher; this.parameterMap = parameterMap; } } }