org.neo4j.ogm.drivers.http.driver.HttpDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.neo4j.ogm.drivers.http.driver.HttpDriver.java

Source

/*
 * Copyright (c) 2002-2018 "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 java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

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.config.Configuration;
import org.neo4j.ogm.config.ObjectMapperFactory;
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.CypherException;
import org.neo4j.ogm.request.DefaultRequest;
import org.neo4j.ogm.request.OptimisticLockingConfig;
import org.neo4j.ogm.request.Request;
import org.neo4j.ogm.request.Statement;
import org.neo4j.ogm.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * @author vince
 */

public final class HttpDriver extends AbstractConfigurableDriver {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpDriver.class);
    private final ObjectMapper mapper = ObjectMapperFactory.objectMapper();

    private CloseableHttpClient httpClient;

    public HttpDriver() {
    }

    public HttpDriver(CloseableHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    @Override
    public void configure(Configuration config) {
        super.configure(config);

        if (config.getVerifyConnection()) {
            httpClient();

            HttpRequest request = new HttpRequest(httpClient(), requestUrl(), configuration.getCredentials(), true);
            request.execute(new VerifyRequest());
        }
    }

    @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, Iterable<String> bookmarks) {
        if (bookmarks != null && bookmarks.iterator().hasNext()) {
            LOGGER.warn("Passing bookmarks {} to HttpDriver. This is not currently supported.", bookmarks);
        }
        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) {
                JsonNode responseNode = mapper.readTree(EntityUtils.toString(responseEntity));
                LOGGER.debug("Response: {}", responseNode);
                JsonNode errors = responseNode.findValue("errors");
                if (errors.elements().hasNext()) {
                    JsonNode errorNode = errors.elements().next();
                    throw new CypherException("Error executing Cypher", errorNode.findValue("code").asText(),
                            errorNode.findValue("message").asText());
                }
            }
            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
    }

    @Override
    public boolean requiresTransaction() {
        return false;
    }

    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;
    }

    private static class VerifyRequest implements DefaultRequest {
        @Override
        public List<Statement> getStatements() {
            return Collections.singletonList(new Statement() {
                @Override
                public String getStatement() {
                    return "RETURN 1";
                }

                @Override
                public Map<String, Object> getParameters() {
                    return Collections.emptyMap();
                }

                @Override
                public String[] getResultDataContents() {
                    return new String[0];
                }

                @Override
                public boolean isIncludeStats() {
                    return false;
                }

                @Override
                public Optional<OptimisticLockingConfig> optimisticLockingConfig() {
                    return Optional.empty();
                }
            });
        }
    }
}