/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product may include a number of subcomponents with * separate copyright notices and license terms. Your use of the source * code for these subcomponents is subject to the terms and * conditions of the subcomponent's license, as noted in the LICENSE file. */ package org.neo4j.ogm.drivers.http.driver; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import java.io.IOException; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.util.EntityUtils; import org.neo4j.ogm.driver.AbstractConfigurableDriver; import org.neo4j.ogm.drivers.http.request.HttpRequest; import org.neo4j.ogm.drivers.http.request.HttpRequestException; import org.neo4j.ogm.drivers.http.transaction.HttpTransaction; import org.neo4j.ogm.exception.ResultErrorsException; import org.neo4j.ogm.request.Request; import org.neo4j.ogm.transaction.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author vince */ public final class HttpDriver extends AbstractConfigurableDriver { private static final Logger LOGGER = LoggerFactory.getLogger(HttpDriver.class); private CloseableHttpClient httpClient; public HttpDriver() { } public HttpDriver(CloseableHttpClient httpClient) { this.httpClient = httpClient; } @Override public synchronized void close() { try { LOGGER.info("Shutting down Http driver {} ", this); if (httpClient != null) { httpClient.close(); } } catch (Exception e) { LOGGER.warn("Unexpected Exception when closing http client httpClient: ", e); } } @Override public Request request() { Transaction tx = transactionManager.getCurrentTransaction(); if (tx == null) { return new HttpRequest(httpClient(), requestUrl(), configuration.getCredentials()); } else { return new HttpRequest(httpClient(), requestUrl(), configuration.getCredentials(), tx.isReadOnly()); } } @Override public Transaction newTransaction(Transaction.Type type, String bookmark) { return new HttpTransaction(transactionManager, this, newTransactionUrl(type), type); } public CloseableHttpResponse executeHttpRequest(HttpRequestBase request) throws HttpRequestException { try (CloseableHttpResponse response = HttpRequest.execute(httpClient(), request, configuration.getCredentials())) { HttpEntity responseEntity = response.getEntity(); if (responseEntity != null) { String responseText; responseText = EntityUtils.toString(responseEntity); LOGGER.debug("Thread: {}, text: {}", Thread.currentThread().getId(), responseText); EntityUtils.consume(responseEntity); if (responseText.contains("\"errors\":[{") || responseText.contains("\"errors\": [{")) { throw new ResultErrorsException(responseText); } } return response; } catch (IOException ioe) { throw new HttpRequestException(request, ioe); } finally { request.releaseConnection(); LOGGER.debug("Thread: {}, Connection released", Thread.currentThread().getId()); } } private String newTransactionUrl(Transaction.Type type) { String url = transactionEndpoint(configuration.getURI()); LOGGER.debug("Thread: {}, POST {}", Thread.currentThread().getId(), url); HttpPost request = new HttpPost(url); request.setHeader("X-WRITE", type == Transaction.Type.READ_ONLY ? "0" : "1"); try (CloseableHttpResponse response = executeHttpRequest(request)) { Header location = response.getHeaders("Location")[0]; return location.getValue(); } catch (IOException ioe) { throw new HttpRequestException(request, ioe); } } private String autoCommitUrl() { return transactionEndpoint(configuration.getURI()).concat("/commit"); } private String transactionEndpoint(String server) { if (server == null) { return null; } String url = server; if (!server.endsWith("/")) { url += "/"; } return url + "db/data/transaction"; } private String requestUrl() { if (transactionManager != null) { Transaction tx = transactionManager.getCurrentTransaction(); if (tx != null) { LOGGER.debug("Thread: {}, request url {}", Thread.currentThread().getId(), ((HttpTransaction) tx).url()); return ((HttpTransaction) tx).url(); } else { LOGGER.debug("Thread: {}, No current transaction, using auto-commit", Thread.currentThread().getId()); } } else { LOGGER.debug("Thread: {}, No transaction manager available, using auto-commit", Thread.currentThread().getId()); } LOGGER.debug("Thread: {}, request url {}", Thread.currentThread().getId(), autoCommitUrl()); return autoCommitUrl(); } public boolean readOnly() { if (transactionManager != null) { Transaction tx = transactionManager.getCurrentTransaction(); if (tx != null) { return tx.isReadOnly(); } } return false; // its read-write by default } private synchronized CloseableHttpClient httpClient() { if (httpClient == null) { // most of the time this will be false, branch-prediction will be very fast and the lock released immediately try { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); SSLContext sslContext = SSLContext.getDefault(); if (configuration.getTrustStrategy() != null) { if (configuration.getTrustStrategy().equals("ACCEPT_UNSIGNED")) { sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build(); LOGGER.warn("Certificate validation has been disabled"); } } // setup the default or custom ssl context httpClientBuilder.setSSLContext(sslContext); HostnameVerifier hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier(); SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslSocketFactory) .build(); // allows multi-threaded use PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); Integer connectionPoolSize = configuration.getConnectionPoolSize(); connectionManager.setMaxTotal(connectionPoolSize); connectionManager.setDefaultMaxPerRoute(connectionPoolSize); httpClientBuilder.setConnectionManager(connectionManager); httpClient = httpClientBuilder.build(); } catch (Exception e) { throw new RuntimeException(e); } } return httpClient; } }