
org.neo4j.jdbc.bolt.impl.BoltNeo4jDriverImpl Maven / Gradle / Ivy
Show all versions of neo4j-jdbc-bolt Show documentation
/*
* 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.
*/
package org.neo4j.jdbc.bolt.impl;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
import org.neo4j.jdbc.Neo4jDriver;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* @author AgileLARUS
* @since 3.3.1
*/
public abstract class BoltNeo4jDriverImpl extends Neo4jDriver {
public static final String TRUST_STRATEGY_KEY = "trust.strategy";
public static final String TRUSTED_CERTIFICATE_KEY = "trusted.certificate.file";
public static final String CONNECTION_ACQUISITION_TIMEOUT = "connection.acquisition.timeout";
public static final String CONNECTION_LIVENESS_CHECK_TIMEOUT = "connection.liveness.check.timeout";
public static final String CONNECTION_TIMEOUT = "connection.timeout";
public static final String ENCRYPTION = "encryption";
public static final String LEAKED_SESSIONS_LOGGING = "leaked.sessions.logging";
public static final String MAX_CONNECTION_LIFETIME = "max.connection.lifetime";
public static final String MAX_CONNECTION_POOLSIZE = "max.connection.poolsize";
public static final String MAX_TRANSACTION_RETRY_TIME = "max.transaction.retry.time";
public static final String DATABASE = "database";
protected BoltNeo4jDriverImpl(String prefix) {
super(prefix);
}
@Override
public Connection connect(String url, Properties props) throws SQLException {
if (url == null) {
throw new SQLException("null is not a valid url");
}
Connection connection = null;
if (acceptsURL(url)) {
String boltUrl = url.replace(Neo4jDriver.JDBC_PREFIX, "").replaceAll("^(" + getPrefix() + ":)([^/])", "$1//$2");
try {
Properties info = mergeUrlAndInfo(boltUrl, props);
boltUrl = removeUrlProperties(boltUrl);
Config.ConfigBuilder builder = Config.builder();
if (info.containsKey("nossl")) {
builder = builder.withoutEncryption();
}
builder = setTrustStrategy(info, builder);
builder = setConnectionAcquisitionTimeout(info, builder);
builder = setIdleTimeBeforeConnectionTest(info, builder);
builder = setConnectionTimeout(info, builder);
builder = setEncryption(info, builder);
builder = setLakedSessionLogging(info, builder);
builder = setMaxConnectionLifetime(info, builder);
builder = setMaxConnectionPoolSize(info, builder);
builder = setMaxTransactionRetryTime(info, builder);
Config config = builder.build();
AuthToken authToken = getAuthToken(info);
Properties routingContext = getRoutingContext(boltUrl, info);
boltUrl = addRoutingPolicy(boltUrl, routingContext);
List routingUris = buildRoutingUris(boltUrl, routingContext);
Driver driver = getDriver(routingUris, config, authToken, info);
driver.verifyConnectivity();
connection = BoltNeo4jConnectionImpl.newInstance(driver, info, url);
} catch (Exception e) {
throw new SQLException(e);
}
}
return connection;
}
protected abstract Driver getDriver(List routingUris, Config config, AuthToken authToken, Properties info) throws URISyntaxException;
private AuthToken getAuthToken(Properties properties) {
if (!properties.containsKey("user") ) {
if(properties.containsKey("password")){
//if only password is provided, try to authenticate with the default user: 'neo4j'
return AuthTokens.basic("neo4j", properties.getProperty("password"));
}
//neither user nor password
return AuthTokens.none();
}
//user provided, it need a password
return AuthTokens.basic(properties.getProperty("user"), properties.getProperty("password"));
}
private String removeUrlProperties(String url) {
String boltUrl = url;
if (boltUrl.indexOf('?') != -1) {
boltUrl = url.substring(0, url.indexOf('?'));
}
return boltUrl;
}
protected abstract Properties getRoutingContext(String url, Properties properties);
protected abstract String addRoutingPolicy(String url, Properties properties);
protected abstract List buildRoutingUris(String boltUrl, Properties properties) throws URISyntaxException;
private Config.ConfigBuilder setTrustStrategy(Properties properties, Config.ConfigBuilder builder) throws SQLException {
Config.ConfigBuilder newBuilder = builder;
if (properties.containsKey(TRUST_STRATEGY_KEY)) {
Config.TrustStrategy.Strategy strategy;
try {
strategy = Config.TrustStrategy.Strategy.valueOf((String) properties.get(TRUST_STRATEGY_KEY));
} catch (IllegalArgumentException e) {
throw new SQLException("Invalid value for trust.strategy param.", e);
}
switch (strategy) {
case TRUST_SYSTEM_CA_SIGNED_CERTIFICATES:
newBuilder = builder.withTrustStrategy(Config.TrustStrategy.trustSystemCertificates());
break;
case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES:
newBuilder = handleTrustStrategyWithFile(properties, strategy, builder);
break;
case TRUST_ALL_CERTIFICATES:
default:
newBuilder = builder.withTrustStrategy(Config.TrustStrategy.trustAllCertificates());
break;
}
}
return newBuilder;
}
/**
* Mix the properties from input and url, using the URL value when conflicts
* @param url The url of the connection
* @param params The properties passed in the connect method
* @return the merge of the properties from url and input
*/
protected Properties mergeUrlAndInfo(String url, Properties params) {
Properties fromInput = (params==null)?new Properties():params;
Properties fromUrl = super.parseUrlProperties(url, null);
Properties merge = (Properties) fromInput.clone();
Set keys = fromUrl.stringPropertyNames();
for (String key : keys) {
merge.put(key, fromUrl.get(key));
}
setUserInfo(merge);
return merge;
}
/**
* If there're properties values for the Username, it sets the "user" property with that value
* @param properties
*/
protected void setUserInfo(Properties properties) {
// 'username' key has highest priority over 'user' key
String user = properties.getProperty("username");
if(user == null){
user = properties.getProperty("user");
}
if (user!=null && !user.trim().isEmpty()) {
properties.setProperty("user",user);
}
}
private Config.ConfigBuilder handleTrustStrategyWithFile(Properties properties, Config.TrustStrategy.Strategy strategy, Config.ConfigBuilder builder)
throws SQLException {
if (properties.containsKey(TRUSTED_CERTIFICATE_KEY)) {
Config.ConfigBuilder newBuilder;
switch (strategy) {
case TRUST_CUSTOM_CA_SIGNED_CERTIFICATES:
String value = properties.getProperty(TRUSTED_CERTIFICATE_KEY);
newBuilder = builder.withTrustStrategy(Config.TrustStrategy.trustCustomCertificateSignedBy(new File(value)));
break;
default:
newBuilder = builder;
break;
}
return newBuilder;
} else {
throw new SQLException("Missing parameter 'trusted.certificate.file' : A FILE IS REQUIRED");
}
}
/**
* Get a value from the properties and try to apply it to the builder
* @param info
* @param builder
* @param key
* @param op
* @param errorMessage
* @return
*/
private Config.ConfigBuilder setValueConfig(Properties info, Config.ConfigBuilder builder, String key, Function op, String errorMessage) {
if(info.containsKey(key)){
String value = info.getProperty(key);
try{
return op.apply(value);
}catch(Exception e){
throw new IllegalArgumentException(key+": "+value+" "+errorMessage);
}
}
return builder;
}
/**
* Get a long value from the properties and apply it to the builder
* @param info
* @param builder
* @param key
* @param op
* @return
*/
private Config.ConfigBuilder setLongConfig(Properties info, Config.ConfigBuilder builder, String key, Function op) {
return setValueConfig(info, builder, key, (val)->op.apply(Long.parseLong(val)), "is not a number");
}
/**
* Get a boolean value from the properties and apply it to the builder
* @param info
* @param builder
* @param key
* @param op
* @return
*/
private Config.ConfigBuilder setBooleanConfig(Properties info, Config.ConfigBuilder builder, String key, Function op) {
return setValueConfig(info, builder, key, (val)->{
if ("true".equalsIgnoreCase(val) || "false".equalsIgnoreCase(val)) {
return op.apply(Boolean.parseBoolean(val));
}else{
throw new IllegalArgumentException();
}
}, "is not a boolean");
}
/**
* Configure CONNECTION_ACQUISITION_TIMEOUT
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setConnectionAcquisitionTimeout(Properties info, Config.ConfigBuilder builder) {
return setLongConfig(info, builder, CONNECTION_ACQUISITION_TIMEOUT, (ms)->builder.withConnectionAcquisitionTimeout(ms, TimeUnit.MILLISECONDS));
}
/**
* Configure CONNECTION_LIVENESS_CHECK_TIMEOUT
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setIdleTimeBeforeConnectionTest(Properties info, Config.ConfigBuilder builder) {
return setLongConfig(info, builder, CONNECTION_LIVENESS_CHECK_TIMEOUT, (ms)->builder.withConnectionLivenessCheckTimeout(ms, TimeUnit.MINUTES));
}
/**
* Configure CONNECTION_TIMEOUT
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setConnectionTimeout(Properties info, Config.ConfigBuilder builder) {
return setLongConfig(info, builder, CONNECTION_TIMEOUT, (ms)->builder.withConnectionTimeout(ms, TimeUnit.MILLISECONDS));
}
/**
* Configure ENCRYPTION
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setEncryption(Properties info, Config.ConfigBuilder builder) {
return setBooleanConfig(info, builder, ENCRYPTION, (condition)-> (condition)?builder.withEncryption():builder.withoutEncryption());
}
/**
* Configure LEAKED_SESSIONS_LOGGING
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setLakedSessionLogging(Properties info, Config.ConfigBuilder builder) {
return setBooleanConfig(info, builder, LEAKED_SESSIONS_LOGGING, (condition)-> (condition)?builder.withLeakedSessionsLogging():builder);
}
/**
* Configure MAX_CONNECTION_LIFETIME
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setMaxConnectionLifetime(Properties info, Config.ConfigBuilder builder) {
return setLongConfig(info, builder, MAX_CONNECTION_LIFETIME, (ms)->builder.withMaxConnectionLifetime(ms, TimeUnit.MILLISECONDS));
}
/**
* Configure MAX_CONNECTION_POOLSIZE
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setMaxConnectionPoolSize(Properties info, Config.ConfigBuilder builder) {
return setValueConfig(info, builder, MAX_CONNECTION_POOLSIZE, (val)->builder.withMaxConnectionPoolSize(Integer.parseInt(val)),"is not a number");
}
/**
* Configure MAX_TRANSACTION_RETRY_TIME
* @param info
* @param builder
* @return always a builder
*/
private Config.ConfigBuilder setMaxTransactionRetryTime(Properties info, Config.ConfigBuilder builder) {
return setLongConfig(info, builder, MAX_TRANSACTION_RETRY_TIME, (ms)->builder.withMaxTransactionRetryTime(ms, TimeUnit.MILLISECONDS));
}
}