/************************************************************************ * Copyright (c) 2014-2015 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 iot.jcypher.database.DBProperties; import iot.jcypher.database.DBType; import iot.jcypher.database.internal.DBUtil; import iot.jcypher.database.internal.IDBAccessInit; import iot.jcypher.query.JcQuery; import iot.jcypher.query.JcQueryResult; import iot.jcypher.query.result.JcError; import iot.jcypher.query.writer.ContextAccess; import iot.jcypher.query.writer.JSONWriter; import iot.jcypher.query.writer.WriterContext; import iot.jcypher.transaction.ITransaction; import iot.jcypher.util.Base64CD; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonReader; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.Invocation.Builder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.StatusType; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.value.StringValue; import org.neo4j.driver.v1.AuthToken; import org.neo4j.driver.v1.Value; public class RemoteDBAccess extends AbstractRemoteDBAccess { private static final String transactionalURLPostfix = "db/data/transaction/commit"; private static final String locationHeader = "Location"; static final String authHeader = "Authorization"; private static final String authBasic = "Basic"; private ThreadLocal<RTransactionImpl> transaction = new ThreadLocal<RTransactionImpl>(); private String auth; private Client restClient; private WebTarget transactionalTarget; private Invocation.Builder invocationBuilder; @Override public List<JcQueryResult> execute(List<JcQuery> queries) { WriterContext context = new WriterContext(); ContextAccess.getResultDataContents(context).add("rest"); ContextAccess.getResultDataContents(context).add("graph"); JSONWriter.toJSON(queries, context); String json = context.buffer.toString(); Response response = null; Throwable exception = null; Builder iBuilder; RTransactionImpl tx = null; if ((tx = this.transaction.get()) != null) { iBuilder = tx.getInvocationBuilder(); } else { iBuilder = getInvocationBuilder(); } try { response = iBuilder.post(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE)); } catch(Throwable e) { exception = e; tx = null; } if (tx != null) { String txLocation = response.getHeaderString(locationHeader); tx.setTxLocation(txLocation); } JsonObject jsonResult = null; StatusType status = null; if (exception == null) { status = response.getStatusInfo(); String result = response.readEntity(String.class); if (result != null && result.length() > 0) { StringReader sr = new StringReader(result); JsonReader reader = Json.createReader(sr); jsonResult = reader.readObject(); } } 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))); } else if (status != null && status.getStatusCode() >= 400) { String code = String.valueOf(status.getStatusCode()); String msg = status.getReasonPhrase(); qr.addGeneralError(new JcError(code, msg, null)); } } return ret; } @Override public ITransaction beginTX() { RTransactionImpl tx = this.transaction.get(); if (tx == null) { tx = new RTransactionImpl(this); this.transaction.set(tx); } return tx; } @Override public ITransaction getTX() { return this.transaction.get(); } @Override public synchronized void close() { super.close(); if (this.restClient != null) { this.restClient.close(); this.restClient = null; } this.transactionalTarget = null; this.invocationBuilder = null; } @Override public void setAuth(String userId, String password) { try { StringBuilder sb = new StringBuilder(); sb.append(userId); sb.append(':'); sb.append(password); byte[] bytes = sb.toString().getBytes("UTF-8"); sb = new StringBuilder(); sb.append(authBasic); sb.append(' '); sb.append(new String(Base64CD.encode(bytes))); this.auth = sb.toString(); } catch (Throwable e) { throw new RuntimeException(e); } } @Override public void setAuth(AuthToken authToken) { if (authToken instanceof InternalAuthToken) { Map<String, Value> map = ((InternalAuthToken)authToken).toMap(); Value scheme = map.get("scheme"); if (scheme != null) { if ("basic".equals(scheme.asString())) { String uid = map.get("principal") != null ? map.get("principal").asString() : null; String pw = map.get("credentials") != null ? map.get("credentials").asString() : null; if (uid != null && pw != null) this.setAuth(uid, pw); } } } } public String getAuth() { // public for testing return this.auth; } synchronized Client getRestClient() { if (this.restClient == null) { this.restClient = ClientBuilder.newClient(); this.shutdownHook = registerShutdownHook(this.restClient); } return this.restClient; } String getServerRootUri() { return this.properties.getProperty(DBProperties.SERVER_ROOT_URI); } void removeTx() { this.transaction.remove(); } private synchronized WebTarget getTransactionalTarget() { if (this.transactionalTarget == null) { WebTarget serverRootTarget = getRestClient().target( this.properties.getProperty(DBProperties.SERVER_ROOT_URI)); this.transactionalTarget = serverRootTarget.path(transactionalURLPostfix); } return this.transactionalTarget; } private synchronized Invocation.Builder getInvocationBuilder() { if (this.invocationBuilder == null) { this.invocationBuilder = getTransactionalTarget().request(MediaType.APPLICATION_JSON_TYPE); if (this.auth != null) this.invocationBuilder = this.invocationBuilder.header(authHeader, this.auth); } return this.invocationBuilder; } private static Thread registerShutdownHook(final Client client) { // 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 { client.close(); } catch (Throwable e) { // do nothing } } }; Runtime.getRuntime().addShutdownHook(hook); return hook; } }