/************************************************************************
* Copyright (c) 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.remote;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.neo4j.driver.v1.AuthToken;
import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.Transaction;
import iot.jcypher.database.DBProperties;
import iot.jcypher.database.internal.DBUtil;
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.QueryParam;
import iot.jcypher.query.writer.WriterContext;
import iot.jcypher.transaction.ITransaction;
public class BoltDBAccess extends AbstractRemoteDBAccess {
private static final String pathPrefix = "://";
private static final String bolt = "bolt";
private ThreadLocal<BoltTransactionImpl> transaction = new ThreadLocal<BoltTransactionImpl>();
private AuthToken authToken;
private Driver driver;
private Session session;
public BoltDBAccess() {
super();
this.shutdownHook = registerShutdownHook(this);
}
@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));
}
Transaction tx;
BoltTransactionImpl btx = this.transaction.get();
if (btx != null)
tx = btx.getTransaction();
else
tx = getSession().beginTransaction();
Throwable dbException = null;
List<JcQueryResult> ret = new ArrayList<JcQueryResult>(queries.size());
StatementResult result;
try {
for (Statement statement : statements) {
if (statement.parameterMap != null)
result = tx.run(statement.cypher, statement.parameterMap);
else
result = tx.run(statement.cypher);
ret.add(new JcQueryResult(result, this));
}
if (btx == null)
tx.success();
} catch (Throwable e) {
dbException = e;
if (btx != null)
btx.failure();
tx.failure();
} finally {
if (btx == null && tx != null) {
try {
tx.close();
} catch(Throwable e1) {
dbException = e1;
}
}
}
if (dbException != null) {
String typ = dbException.getClass().getSimpleName();
String msg = dbException.getLocalizedMessage();
JcError err = new JcError(typ, msg, DBUtil.getStacktrace(dbException));
if (ret.size() < queries.size()) {
for(int i = ret.size(); i < queries.size(); i++) {
JcQueryResult res = new JcQueryResult(null, this);
res.getDBErrors().add(err);
ret.add(res);
}
} else {
JcQueryResult res = ret.get(ret.size() - 1);
res.getDBErrors().add(err); // the last one must have been erroneous
}
}
return ret;
}
@Override
public ITransaction beginTX() {
BoltTransactionImpl tx = this.transaction.get();
if (tx == null) {
tx = new BoltTransactionImpl(this);
this.transaction.set(tx);
}
return tx;
}
@Override
public ITransaction getTX() {
return this.transaction.get();
}
@Override
public void setAuth(String userId, String password) {
if (userId != null && password != null)
this.authToken = AuthTokens.basic(userId, password);
}
@Override
public void setAuth(AuthToken authToken) {
this.authToken = authToken;
}
void removeTx() {
this.transaction.remove();
}
private Driver getDriver() {
if (this.driver == null) {
String uri = this.properties.getProperty(DBProperties.SERVER_ROOT_URI);
if (this.authToken != null)
this.driver = GraphDatabase.driver(uri, this.authToken);
else
this.driver = GraphDatabase.driver(uri);
}
return this.driver;
}
public synchronized Session getSession() {
if (this.session == null)
this.session = getDriver().session();
return this.session;
}
public static boolean isBoltProtocol(String uri) {
boolean ret = false;
if (uri != null) {
int idx = uri.indexOf(pathPrefix);
if (idx > 0) {
String prot = uri.substring(0, idx);
ret = bolt.equalsIgnoreCase(prot);
}
}
return ret;
}
private static Thread registerShutdownHook(final BoltDBAccess bda) {
// Registers a shutdown hook
// shuts down nicely when the VM exits (even if you "Ctrl-C" the
// running application).
Thread hook = new Thread() {
@Override
public void run() {
try {
if (bda.session != null)
bda.session.close();
} catch (Throwable e) {
// do nothing
}
try {
if (bda.driver != null)
bda.driver.close();
} catch (Throwable e) {
// do nothing
}
}
};
Runtime.getRuntime().addShutdownHook(hook);
return hook;
}
/*******************************************/
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;
}
}
}