All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.neo4j.jdbc.http.driver.CypherExecutor Maven / Gradle / Ivy

There is a newer version: 4.0.10
Show newest version
/*
 * Copyright (c) 2016 LARUS Business Automation [http://www.larus-ba.it]
 * 

* This file is part of the "LARUS Integration Framework for Neo4j". *

* 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 *

* 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. *

* Created on 15/4/2016 */ package org.neo4j.jdbc.http.driver; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.Header; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.AuthState; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.BasicScheme; 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 org.apache.http.protocol.HttpContext; 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.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.Logger; import java.util.stream.Collectors; import static java.util.stream.Collectors.toList; import static org.neo4j.jdbc.Neo4jDatabaseMetaData.GET_DBMS_FUNCTIONS; /** * 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; /** * Name of the database (default: neo4j) */ private final String databaseName; /** * Jackson mapper object. */ private static final ObjectMapper mapper = new ObjectMapper(); private static final String DB_DATA_TRANSACTION_TEMPLATE = "/db/%s/tx"; private static final Logger LOGGER = Logger.getLogger(CypherExecutor.class.getCanonicalName()); static { mapper.configure(DeserializationFeature.USE_LONG_FOR_INTS, true); } private class PreemptiveAuthInterceptor implements HttpRequestInterceptor { public void process(final HttpRequest request, final HttpContext context) throws HttpException { AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE); // If no auth scheme available yet, try to initialize it preemptively if (authState.getAuthScheme() == null) { CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER); HttpHost targetHost = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST); Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort())); if (creds == null) { throw new HttpException("No credentials for preemptive authentication"); } authState.update(new BasicScheme(), creds); } } } /** * 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. * @throws SQLException sqlexception */ public CypherExecutor(String host, Integer port, Boolean secure, Properties properties) throws SQLException { this.secure = secure; // Create the http client builder HttpClientBuilder builder = HttpClients.custom(); // Setting user-agent String userAgent = properties.getProperty("useragent"); builder.setUserAgent(getUserAgent(userAgent)); // Adding authentication to the http client if needed try { if (isAuthenticationRequired(host, port, secure, properties)) { CredentialsProvider credentialsProvider = getCredentialsProvider(host, port, properties); if (credentialsProvider == null) { throw new SQLException("Authentication required"); } builder.setDefaultCredentialsProvider(credentialsProvider); builder.addInterceptorFirst(new PreemptiveAuthInterceptor()); } } catch (SQLException e) { throw e; } catch (Exception e) { throw new SQLException(e.getMessage()); } // Create the http client this.http = builder.build(); // Create the url endpoint this.databaseName = String.valueOf(properties.getOrDefault("database", "neo4j")); this.transactionUrl = createTransactionUrl(this.databaseName, host, port, this.secure); // Setting autocommit this.setAutoCommit(Boolean.valueOf(properties.getProperty("autoCommit", "true"))); } public String getUserAgent(String userAgent) { return "Neo4j JDBC Driver" + (userAgent != null ? " via "+userAgent : ""); } public boolean isAuthenticationRequired(String host, Integer port, Boolean secure, Properties properties) throws Exception { HttpUriRequest request = RequestBuilder.head() .setUri(new URL(secure ? "https" : "http", host, port, "/db/data/").toURI()) .build(); try (CloseableHttpClient minimalHttp = HttpClients .custom() .disableAutomaticRetries() .setUserAgent(getUserAgent(properties.getProperty("useragent"))) .build()) { HttpResponse response = minimalHttp.execute(request); return response.getStatusLine().getStatusCode() == 401; } } private String createTransactionUrl(String databaseName, String host, Integer port, Boolean secure) throws SQLException { String transactionPath = transactionPath(databaseName); try { if (secure) { return new URL("https", host, port, transactionPath).toString(); } return new URL("http", host, port, transactionPath).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 the response for these queries * @throws SQLException sqlexception */ public Neo4jResponse executeQueries(List 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); } public List callDbmsFunctions() { try { Neo4jResponse response = this.executeQuery(new Neo4jStatement(GET_DBMS_FUNCTIONS, Collections.emptyMap(), false)); if (response.hasErrors()) { return Collections.emptyList(); } return response.getResults() .stream() .flatMap(result -> result.getRows() .stream() .map(rows -> { Iterable rowData = (Iterable) rows.get("row"); return (String) rowData.iterator().next(); })) .distinct() .collect(Collectors.toList()); } catch (SQLException e) { LOGGER.warning(String.format("Could not retrieve DBMS functions:%n%s", e)); return Collections.emptyList(); } } /** * Execute a cypher query. * * @param query Cypher query object. * @return the response for this query * @throws SQLException sqlexception */ public Neo4jResponse executeQuery(Neo4jStatement query) throws SQLException { List queries = new ArrayList<>(); queries.add(query); return this.executeQueries(queries); } /** * Commit the current transaction. * * @throws SQLException 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; } } /** * 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.getCode() != 200 & response.hasErrors()) { throw new SQLException(response.displayErrors()); } this.currentTransactionUrl = this.transactionUrl; } } /** * Getter for AutoCommit. * * @return true if statement are automatically committed */ public Boolean getAutoCommit() { return autoCommit; } /** * Setter for autocommit. * * @param autoCommit enable/disable autocommit * @throws SQLException 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 = 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 = null; // Prepare the headers query HttpGet request = new HttpGet(this.transactionUrl .replace(transactionPath(this.databaseName), "/db/data")); // 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); final String neo4j_version = (String) body.get("neo4j_version"); if (neo4j_version != null) { result = neo4j_version; } } } catch (Exception e) { result = "Unknown"; } return result; } /** * Close all thing in this object. * @throws SQLException sqlexception */ 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, -1 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 -1 if there is no transaction. */ public Integer getOpenTransactionId() { return getTransactionId(this.currentTransactionUrl); } private String transactionPath(String databaseName) { return String.format(DB_DATA_TRANSACTION_TEMPLATE, databaseName); } /** * 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; // 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(!getAutoCommit()) { 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.getLocation() != 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.getLocation()); this.currentTransactionUrl = this.transactionUrl + "/" + transactionId; } } } catch (Exception e) { throw new SQLException(e); } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy