it.larusba.neo4j.jdbc.http.driver.CypherExecutor.java Source code

Java tutorial

Introduction

Here is the source code for it.larusba.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 it.larusba.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.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.
     */
    protected final String transactionUrl;

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

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

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

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

    /**
     * Default constructor.
     *
     * @param host       Hostname of the Neo4j instance.
     * @param port       HTTP port of the Neo4j instance.
     * @param properties Properties of the url connection.
     * @throws SQLException
     */
    public CypherExecutor(String host, Integer port, Properties properties) throws SQLException {
        // Create the http client builder
        HttpClientBuilder builder = HttpClients.custom();
        // Adding authentication to the http client if needed
        if (properties.containsKey("user") && properties.containsKey("password")) {
            CredentialsProvider credsProvider = new BasicCredentialsProvider();
            credsProvider.setCredentials(new AuthScope(host, port), new UsernamePasswordCredentials(
                    properties.get("user").toString(), properties.get("password").toString()));
            builder.setDefaultCredentialsProvider(credsProvider);
        }
        // Setting user-agent
        StringBuilder sb = new StringBuilder();
        sb.append("Neo4j JDBC Driver");
        if (properties.containsKey("userAgent")) {
            sb.append(" via ");
            sb.append(properties.getProperty("userAgent"));
        }
        builder.setUserAgent(sb.toString());
        // Create the http client
        this.http = builder.build();

        // Create the url endpoint
        StringBuffer sbEndpoint = new StringBuffer();
        sbEndpoint.append("http://").append(host).append(":").append(port).append("/db/data/transaction");
        this.transactionUrl = sbEndpoint.toString();

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

    /**
     * Execute a list of cypher queries.
     *
     * @param queries List of cypher query object
     * @return A list of Neo4j response
     * @throws SQLException
     */
    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, this.mapper),
                ContentType.APPLICATION_JSON);
        request.setEntity(requestEntity);

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

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

    /**
     * Commit the current transaction.
     *
     * @throws SQLException
     */
    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;
        } else {
            throw new SQLException("There is no transaction to commit");
        }
    }

    /**
     * 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;
        } else {
            throw new SQLException("There is no transaction to rollback");
        }
    }

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

    /**
     * Setter for autocommit.
     *
     * @param autoCommit
     * @throws SQLException
     */
    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 = new StringBuffer().append(this.transactionUrl).append("/commit")
                        .toString();
            } 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);
                result = (String) body.getOrDefault("version", result);
            }
        } 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
     */
    protected 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.
     */
    protected 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
     * @throws SQLException
     */
    protected 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, this.mapper);
            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 = new StringBuffer().append(this.transactionUrl).append("/")
                        .append(transactionId).toString();
            }
        } catch (Exception e) {
            throw new SQLException(e);
        }

        return result;
    }

}