org.neo4j.jdbc.http.driver.CypherExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.neo4j.jdbc.http.driver.CypherExecutor.java

Source

/*
 * Copyright (c) 2016 LARUS Business Automation [http://www.larus-ba.it]
 * <p>
 * This file is part of the "LARUS Integration Framework for Neo4j".
 * <p>
 * The "LARUS Integration Framework for Neo4j" is 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.
 * <p>
 * Created on 15/4/2016
 */
package org.neo4j.jdbc.http.driver;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.Header;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Execute cypher queries.
 */
public class CypherExecutor {

    /**
     * URL of the transaction endpoint.
     */
    final String transactionUrl;

    /**
     * If we are in https or not.
     */
    private Boolean secure;

    /**
     * Autocommit transaction.
     * Must be null at creation time.
     * Initiation of this property is made by its setter, that is called from the constructor.
     */
    private Boolean autoCommit;

    /**
     * The http client.
     */
    private CloseableHttpClient http;

    /**
     * URL of the current transaction.
     */
    private String currentTransactionUrl;

    /**
     * Jackson mapper object.
     */
    private final static ObjectMapper mapper = new ObjectMapper();

    /**
     * Default constructor.
     *
     * @param host       Hostname of the Neo4j instance.
     * @param port       HTTP port of the Neo4j instance.
     * @param secure    If the connection used SSL.
     * @param properties Properties of the url connection.
     */
    public CypherExecutor(String host, Integer port, Boolean secure, Properties properties) throws SQLException {
        this.secure = secure;

        // Create the http client builder
        HttpClientBuilder builder = HttpClients.custom();

        // Adding authentication to the http client if needed
        CredentialsProvider credentialsProvider = getCredentialsProvider(host, port, properties);
        if (credentialsProvider != null)
            builder.setDefaultCredentialsProvider(credentialsProvider);

        // Setting user-agent
        String userAgent = properties.getProperty("useragent");
        builder.setUserAgent("Neo4j JDBC Driver" + (userAgent != null ? " via " + userAgent : ""));
        // Create the http client
        this.http = builder.build();

        // Create the url endpoint
        this.transactionUrl = createTransactionUrl(host, port, secure);

        // Setting autocommit
        this.setAutoCommit(Boolean.valueOf(properties.getProperty("autoCommit", "true")));
    }

    private String createTransactionUrl(String host, Integer port, Boolean secure) throws SQLException {
        try {
            if (secure)
                return new URL("https", host, port, "/db/data/transaction").toString();
            else
                return new URL("http", host, port, "/db/data/transaction").toString();
        } catch (MalformedURLException e) {
            throw new SQLException("Invalid server URL", e);
        }
    }

    private CredentialsProvider getCredentialsProvider(String host, Integer port, Properties properties) {
        if (properties.containsKey("password")) {
            String user = properties.getProperty("user", properties.getProperty("username", "neo4j"));
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(user,
                    properties.getProperty("password"));
            credsProvider.setCredentials(new AuthScope(host, port), credentials);
            return credsProvider;
        }
        return null;
    }

    /**
     * Execute a list of cypher queries.
     *
     * @param queries List of cypher query object
     * @return A list of Neo4j response
     */
    public Neo4jResponse executeQueries(List<Neo4jStatement> queries) throws SQLException {
        // Prepare the headers query
        HttpPost request = new HttpPost(currentTransactionUrl);

        // Prepare body request
        StringEntity requestEntity = new StringEntity(Neo4jStatement.toJson(queries, mapper),
                ContentType.APPLICATION_JSON);
        request.setEntity(requestEntity);

        // Make the request
        return this.executeHttpRequest(request);
    }

    /**
     * Execute a cypher query.
     *
     * @param query Cypher query object.
     */
    public Neo4jResponse executeQuery(Neo4jStatement query) throws SQLException {
        List<Neo4jStatement> queries = new ArrayList<>();
        queries.add(query);
        return this.executeQueries(queries);
    }

    /**
     * Commit the current transaction.
     *
     */
    public void commit() throws SQLException {
        if (this.getOpenTransactionId() > 0) {
            HttpPost request = new HttpPost(currentTransactionUrl + "/commit");
            Neo4jResponse response = this.executeHttpRequest(request);
            if (response.hasErrors()) {
                throw new SQLException(response.displayErrors());
            }
            this.currentTransactionUrl = this.transactionUrl;
        }
    }

    /**
     * Rollback the current transaction.
     *
     * @throws SQLException if there is no transaction to rollback
     */
    public void rollback() throws SQLException {
        if (this.getOpenTransactionId() > 0) {
            // Prepare the request
            HttpDelete request = new HttpDelete(currentTransactionUrl);
            Neo4jResponse response = this.executeHttpRequest(request);
            if (response.code != 200 & response.hasErrors()) {
                throw new SQLException(response.displayErrors());
            }
            this.currentTransactionUrl = this.transactionUrl;
        }
    }

    /**
     * Getter for AutoCommit.
     */
    public Boolean getAutoCommit() {
        return autoCommit;
    }

    /**
     * Setter for autocommit.
     *
     * @param autoCommit
     */
    public void setAutoCommit(Boolean autoCommit) throws SQLException {
        // we only do something if there is a change
        if (this.autoCommit != autoCommit) {

            if (autoCommit) {
                // Check if a transaction is currently opened before
                // If so, we commit it
                if (getOpenTransactionId() > 0) {
                    this.commit();
                }
                this.autoCommit = Boolean.TRUE;
                this.currentTransactionUrl = this.transactionUrl + "/commit";
            } else {
                this.autoCommit = Boolean.FALSE;
                this.currentTransactionUrl = this.transactionUrl;
            }
        }
    }

    /**
     * Retrieve the Neo4j version from the server.
     *
     * @return A string that represent the neo4j server version
     */
    public String getServerVersion() {
        String result = "Unknown";

        // Prepare the headers query
        HttpGet request = new HttpGet(
                this.transactionUrl.replace("/db/data/transaction", "/db/manage/server/version"));

        // Adding default headers to the request
        for (Header header : this.getDefaultHeaders()) {
            request.addHeader(header.getName(), header.getValue());
        }

        // Make the request
        try (CloseableHttpResponse response = http.execute(request)) {
            try (InputStream is = response.getEntity().getContent()) {
                Map body = mapper.readValue(is, Map.class);
                if (body.get("version") != null) {
                    result = (String) body.get("version");
                }
            }
        } catch (Exception e) {
            // do nothing there is the default value
        }

        return result;
    }

    /**
     * Close all thing in this object.
     */

    public void close() throws SQLException {
        try {
            http.close();
        } catch (IOException e) {
            throw new SQLException(e);
        }
    }

    /**
     * Retrieve the transaction id from an url.
     *
     * @param url An url
     * @return The transaction id if there is an opened transaction, <code>-1</code> otherwise
     */
    Integer getTransactionId(String url) {
        Integer transactId = -1;
        if (url != null && url.contains(transactionUrl)) {
            String[] tab = url.split("/");
            String last = tab[tab.length - 1];
            try {
                transactId = Integer.valueOf(last);
            } catch (NumberFormatException e) {
                transactId = -1;
            }
        }
        return transactId;
    }

    /**
     * Retrieve the current transaction id.
     *
     * @return The transaction id, or <code>-1</code> if there is no transaction.
     */
    public Integer getOpenTransactionId() {
        return getTransactionId(this.currentTransactionUrl);
    }

    /**
     * Give the default http client default header for Neo4j API.
     *
     * @return List of default headers.
     */
    private Header[] getDefaultHeaders() {
        Header[] headers = new Header[2];
        headers[0] = new BasicHeader("Accept", ContentType.APPLICATION_JSON.toString());
        headers[1] = new BasicHeader("X-Stream", "true");

        return headers;
    }

    /**
     * Execute the http client request.
     *
     * @param request The request to make
     */
    private Neo4jResponse executeHttpRequest(HttpRequestBase request) throws SQLException {
        Neo4jResponse result = null;

        // Adding default headers to the request
        for (Header header : this.getDefaultHeaders()) {
            request.addHeader(header.getName(), header.getValue());
        }

        // Make the request
        try (CloseableHttpResponse response = http.execute(request)) {
            result = new Neo4jResponse(response, mapper);
            if (result.hasErrors()) {
                // The transaction *was* rolled back server-side. Whether a transaction existed or not before, it should
                // now be considered rolled back on this side as well.
                this.currentTransactionUrl = this.transactionUrl;
            } else if (result.location != null) {
                // Here we reconstruct the location in case of a proxy, but in this case you should redirect write queries to the master.
                Integer transactionId = this.getTransactionId(result.location);
                this.currentTransactionUrl = this.transactionUrl + "/" + transactionId;
            }
        } catch (Exception e) {
            throw new SQLException(e);
        }

        return result;
    }

}