Repository: neo4j-contrib/neo4j-jdbc (java)

Issue:
Add a retry mechanism like Spring Data and OGM
Add a retry mechanism like Spring Data and OGM:

https://github.com/spring-projects/spring-data-neo4j/blob/main/src/main/java/org/springframework/data/neo4j/core/support/RetryExceptionPredicate.java

Pull request:
fixes #268: Add a retry mechanism like Spring Data and OGM
Add a retry mechanism like Spring Data and OGM with Resilience4j

Patch:
diff --git a/neo4j-jdbc-bolt/pom.xml b/neo4j-jdbc-bolt/pom.xml
index 07469565..be0613e9 100644
--- a/neo4j-jdbc-bolt/pom.xml
+++ b/neo4j-jdbc-bolt/pom.xml
@@ -44,6 +44,11 @@
             <artifactId>jackson-databind</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>io.github.resilience4j</groupId>
+            <artifactId>resilience4j-retry</artifactId>
+        </dependency>
+
         <!-- Performance tests dependencies -->
         <dependency>
             <groupId>org.openjdk.jmh</groupId>
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltDriver.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltDriver.java
index 7ecf419b..aab66ba1 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltDriver.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltDriver.java
@@ -40,7 +40,7 @@ import java.util.Properties;
  */
 public class BoltDriver extends BoltNeo4jDriverImpl {
 
-	public static final String JDBC_BOLT_PREFIX = "bolt";
+	public static final String JDBC_BOLT_PREFIX = "^bolt(\\+s|\\+ssc)?$";
 	private static final BoltDriverCache cache = new BoltDriverCache(params ->
 	{
 		return GraphDatabase.driver(params.getRoutingUris().get(0), params.getAuthToken(), params.getConfig());
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jDatabaseMetaData.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jDatabaseMetaData.java
index d38f2288..40d5ea7a 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jDatabaseMetaData.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jDatabaseMetaData.java
@@ -27,6 +27,7 @@ import org.neo4j.jdbc.Neo4jDatabaseMetaData;
 import org.neo4j.jdbc.bolt.impl.BoltNeo4jConnectionImpl;
 import org.neo4j.jdbc.metadata.Column;
 import org.neo4j.jdbc.metadata.Table;
+import org.neo4j.jdbc.utils.BoltNeo4jUtils;
 import org.neo4j.jdbc.utils.Neo4jInvocationHandler;
 
 import java.lang.reflect.Proxy;
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java
index 934c7a0d..66a244f8 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jPreparedStatement.java
@@ -26,6 +26,7 @@ import org.neo4j.jdbc.Neo4jParameterMetaData;
 import org.neo4j.jdbc.Neo4jPreparedStatement;
 import org.neo4j.jdbc.Neo4jResultSetMetaData;
 import org.neo4j.jdbc.bolt.impl.BoltNeo4jConnectionImpl;
+import org.neo4j.jdbc.utils.BoltNeo4jUtils;
 import org.neo4j.jdbc.utils.Neo4jInvocationHandler;
 
 import java.lang.reflect.Proxy;
@@ -39,7 +40,7 @@ import java.util.Map;
 import java.util.function.Function;
 
 import static java.util.Arrays.copyOf;
-import static org.neo4j.jdbc.bolt.BoltNeo4jUtils.executeInTx;
+import static org.neo4j.jdbc.utils.BoltNeo4jUtils.executeInTx;
 
 /**
  * @author AgileLARUS
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jStatement.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jStatement.java
index 9b95c00a..c2fe3a1a 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jStatement.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jStatement.java
@@ -23,6 +23,7 @@ import org.neo4j.driver.Result;
 import org.neo4j.driver.summary.SummaryCounters;
 import org.neo4j.jdbc.Neo4jStatement;
 import org.neo4j.jdbc.bolt.impl.BoltNeo4jConnectionImpl;
+import org.neo4j.jdbc.utils.BoltNeo4jUtils;
 import org.neo4j.jdbc.utils.Neo4jInvocationHandler;
 
 import java.lang.reflect.Proxy;
@@ -34,7 +35,7 @@ import java.util.ArrayList;
 import java.util.function.Function;
 
 import static java.util.Arrays.copyOf;
-import static org.neo4j.jdbc.bolt.BoltNeo4jUtils.executeInTx;
+import static org.neo4j.jdbc.utils.BoltNeo4jUtils.executeInTx;
 
 /**
  * @author AgileLARUS
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java
index c9373369..e7490aa2 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/impl/BoltNeo4jConnectionImpl.java
@@ -32,7 +32,7 @@ import org.neo4j.jdbc.bolt.BoltNeo4jDatabaseMetaData;
 import org.neo4j.jdbc.bolt.BoltNeo4jPreparedStatement;
 import org.neo4j.jdbc.bolt.BoltNeo4jResultSet;
 import org.neo4j.jdbc.bolt.BoltNeo4jStatement;
-import org.neo4j.jdbc.bolt.BoltNeo4jUtils;
+import org.neo4j.jdbc.utils.BoltNeo4jUtils;
 import org.neo4j.jdbc.boltrouting.BoltRoutingNeo4jDriver;
 import org.neo4j.jdbc.impl.Neo4jConnectionImpl;
 import org.neo4j.jdbc.utils.Neo4jInvocationHandler;
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriver.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriver.java
index d54e11e9..b197aff6 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriver.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriver.java
@@ -40,8 +40,7 @@ import java.util.Properties;
  */
 public class BoltRoutingNeo4jDriver extends BoltNeo4jDriverImpl {
 
-//    public static final String JDBC_BOLT_ROUTING_PREFIX = "bolt\\+routing";
-    public static final String JDBC_BOLT_ROUTING_PREFIX = "neo4j";
+    public static final String JDBC_BOLT_ROUTING_PREFIX = "^neo4j(\\+s|\\+ssc)?$";
 
     public static final String ROUTING_CONTEXT = "routing";
     public static final String ALTERNATIVE_SERVERS = "servers";
@@ -72,6 +71,11 @@ public class BoltRoutingNeo4jDriver extends BoltNeo4jDriverImpl {
         super(prefix);
     }
 
+    private boolean isSchemaMatchPrefix(String url) {
+        URI uri = URI.create(url);
+        return uri.getScheme().matches(this.getPrefix());
+    }
+
     @Override
     protected Driver getDriver(List<URI> routingUris, Config config, AuthToken authToken, Properties info) throws URISyntaxException {
         return cache.getDriver(routingUris, config, authToken, info);
@@ -80,8 +84,8 @@ public class BoltRoutingNeo4jDriver extends BoltNeo4jDriverImpl {
     @Override
     protected Properties getRoutingContext(String url, Properties properties) {
         Properties props = new Properties();
-        if (url.matches("^" + this.getPrefix() + ".*") && properties.containsKey(ROUTING_CONTEXT)) {
-            List<String> routingParams = null;
+        if (isSchemaMatchPrefix(url) && properties.containsKey(ROUTING_CONTEXT)) {
+            List<String> routingParams;
             if (properties.get(ROUTING_CONTEXT) instanceof String) {
                 routingParams = Arrays.asList(properties.getProperty(ROUTING_CONTEXT));
             } else {
@@ -100,7 +104,7 @@ public class BoltRoutingNeo4jDriver extends BoltNeo4jDriverImpl {
     @Override
     protected String addRoutingPolicy(String url, Properties properties) {
         String boltUrl = url;
-        if (boltUrl.matches("^" + this.getPrefix() + ".*") && properties.containsKey(ROUTING_CONTEXT)) {
+        if (isSchemaMatchPrefix(url) && properties.containsKey(ROUTING_CONTEXT)) {
             boltUrl += "?" + properties.getProperty(ROUTING_CONTEXT).replaceAll(LIST_SEPARATOR, CUSTOM_ROUTING_POLICY_SEPARATOR);
         }
         return boltUrl;
@@ -123,4 +127,9 @@ public class BoltRoutingNeo4jDriver extends BoltNeo4jDriverImpl {
         }
         return routingUris;
     }
+
+    // visible for testing
+    protected static void clearCache() {
+        cache.clear();
+    }
 }
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jUtils.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/utils/BoltNeo4jUtils.java
similarity index 73%
rename from neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jUtils.java
rename to neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/utils/BoltNeo4jUtils.java
index 2256a5f2..44e0254a 100644
--- a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/bolt/BoltNeo4jUtils.java
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/utils/BoltNeo4jUtils.java
@@ -1,13 +1,20 @@
-package org.neo4j.jdbc.bolt;
+package org.neo4j.jdbc.utils;
 
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryConfig;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.neo4j.driver.Result;
 import org.neo4j.driver.Transaction;
 import org.neo4j.driver.summary.SummaryCounters;
+import org.neo4j.jdbc.bolt.BoltNeo4jConnection;
 
 import java.sql.SQLException;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
 import java.util.Collections;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.logging.Logger;
 
@@ -16,6 +23,13 @@ import java.util.logging.Logger;
  */
 public class BoltNeo4jUtils {
 
+    public static final RetryConfig RETRY_CONFIG = RetryConfig
+            .custom()
+            .retryOnException(new RetryExceptionPredicate())
+            .waitDuration(Duration.of(1, ChronoUnit.SECONDS))
+            .maxAttempts(10)
+            .build();
+
     /**
      * Calculate, using the summary, how many operations are executed in the statement
      * @param stats
@@ -74,11 +88,18 @@ public class BoltNeo4jUtils {
         return executeInTx(connection, sql, Collections.emptyMap(), body);
     }
 
-    public static Result execute(BoltNeo4jConnection connection,
+    private static Result execute(BoltNeo4jConnection connection,
                                           String statement,
-                                          Map<String, Object> params) {
-        Transaction transaction = connection.getTransaction();
-        return transaction.run(statement, params);
+                                          Map<String, Object> params) throws Exception {
+        return runTransactionWithRetries(connection.getTransaction(), statement, params);
+    }
+
+    public static Result runTransactionWithRetries(Transaction tx,
+                                                   String statement,
+                                                   Map<String, Object> params) throws Exception {
+        return Retry.decorateCallable(Retry.of("retryPool", RETRY_CONFIG),
+                () -> tx.run(statement, params))
+                .call();
     }
 
     public static void closeSafely(AutoCloseable closeable, Logger logger) {
diff --git a/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/utils/RetryExceptionPredicate.java b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/utils/RetryExceptionPredicate.java
new file mode 100644
index 00000000..68dfc44f
--- /dev/null
+++ b/neo4j-jdbc-bolt/src/main/java/org/neo4j/jdbc/utils/RetryExceptionPredicate.java
@@ -0,0 +1,35 @@
+package org.neo4j.jdbc.utils;
+
+import org.neo4j.driver.exceptions.ServiceUnavailableException;
+import org.neo4j.driver.exceptions.SessionExpiredException;
+import org.neo4j.driver.exceptions.TransientException;
+
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+public final class RetryExceptionPredicate implements Predicate<Throwable> {
+
+    private static final Set<String> RETRYABLE_ILLEGAL_STATE_MESSAGES = Collections.unmodifiableSet(new HashSet<>(
+            Arrays.asList("Transaction must be open, but has already been closed.",
+                    "Session must be open, but has already been closed.")));
+
+    @Override
+    public boolean test(Throwable throwable) {
+        if (throwable instanceof IllegalStateException) {
+            String msg = throwable.getMessage();
+            return RETRYABLE_ILLEGAL_STATE_MESSAGES.contains(msg);
+        }
+
+        if (throwable instanceof TransientException) {
+            String code = ((TransientException) throwable).code();
+            return !("Neo.TransientError.Transaction.Terminated".equals(code) ||
+                    "Neo.TransientError.Transaction.LockClientStopped".equals(code));
+        } else {
+            return throwable instanceof SessionExpiredException || throwable instanceof ServiceUnavailableException;
+        }
+    }
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 38f3ff8f..43456589 100644
--- a/pom.xml
+++ b/pom.xml
@@ -97,6 +97,8 @@
 		<maven.plugin.surefire>2.20</maven.plugin.surefire>
 
 		<sonar.jacoco.reportPath>${project.basedir}/../target/jacoco.exec</sonar.jacoco.reportPath>
+
+		<resilience4j.version>1.7.1</resilience4j.version>
 	</properties>
 
 	<!-- ============================= -->
@@ -157,6 +159,13 @@
 				<version>${neo4j.version}</version>
 			</dependency>
 
+			<dependency>
+				<groupId>io.github.resilience4j</groupId>
+				<artifactId>resilience4j-retry</artifactId>
+				<version>${resilience4j.version}</version>
+			</dependency>
+
+
 			<!-- Deps to test framework -->
 			<dependency>
 				<groupId>junit</groupId>


Test patch:
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jDriverUrlTest.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jDriverUrlTest.java
new file mode 100644
index 00000000..05cc59b0
--- /dev/null
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/bolt/BoltNeo4jDriverUrlTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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 23/02/16
+ */
+package org.neo4j.jdbc.bolt;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+import org.neo4j.driver.AuthTokens;
+import org.neo4j.driver.Config;
+import org.neo4j.driver.GraphDatabase;
+import org.neo4j.jdbc.Neo4jDriver;
+import org.neo4j.jdbc.bolt.utils.Mocker;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+
+/**
+ * @author AgileLARUS
+ * @since 3.0.0
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({GraphDatabase.class, Config.TrustStrategy.class, AuthTokens.class})
+@PowerMockRunnerDelegate(Parameterized.class)
+public class BoltNeo4jDriverUrlTest {
+
+	@Rule public ExpectedException expectedEx = ExpectedException.none();
+
+	@Parameterized.Parameter
+	public String completeValidUrl;
+
+	private static org.neo4j.driver.Driver mockedDriver;
+
+	@Parameterized.Parameters
+	public static Iterable<? extends Object> data() {
+		return Arrays.asList("jdbc:neo4j:bolt://test", "jdbc:neo4j:bolt+s://test", "jdbc:neo4j:bolt+ssc://test");
+	}
+
+	@BeforeClass
+	public static void initialize() {
+		mockedDriver = Mocker.mockDriver();
+	}
+
+	@Before
+	public void prepare() {
+		BoltDriver.clearCache();
+	}
+
+	private String getBoltUrl() {
+		return completeValidUrl.replace("jdbc:neo4j:", "");
+	}
+
+	private String getPrefix() {
+		return completeValidUrl.split("://")[0];
+	}
+
+	@Test public void shouldConnectCreateConnection() throws SQLException, URISyntaxException {
+		PowerMockito.mockStatic(GraphDatabase.class);
+		Mockito.when(GraphDatabase.driver(Mockito.eq(new URI(getBoltUrl())), Mockito.eq(AuthTokens.none()), any(Config.class))).thenReturn(mockedDriver);
+
+		Neo4jDriver driver = new BoltDriver();
+		Connection connection = driver.connect(completeValidUrl, null);
+		assertNotNull(connection);
+	}
+
+	@Test public void shouldAcceptURL() throws SQLException {
+		Neo4jDriver driver = new BoltDriver();
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://192.168.0.1:7687"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://192.168.0.1:7687/"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687?routing:policy=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687?routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/?routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687?noSsl&routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/?noSsl&routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687,localhost:7688,localhost:7689/?noSsl&routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/?noSsl&routing:region=europe&country=it&routing:servers=localhost:7688,localhost:7689"));
+	}
+}
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriverTest.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriverTest.java
index 628b4b9d..53c4c8de 100644
--- a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriverTest.java
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriverTest.java
@@ -45,24 +45,6 @@ public class BoltRoutingNeo4jDriverTest {
 
 	@Rule public ExpectedException expectedEx = ExpectedException.none();
 
-	/*------------------------------*/
-	/*          acceptsURL          */
-	/*------------------------------*/
-	@Test public void shouldAcceptURL() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, SQLException {
-		Neo4jDriver driver = new BoltRoutingNeo4jDriver();
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687/"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://192.168.0.1:7687"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://192.168.0.1:7687/"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687?routing:policy=it"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687?routing:region=europe&country=it"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687/?routing:region=europe&country=it"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687?noSsl&routing:region=europe&country=it"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687/?noSsl&routing:region=europe&country=it"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687,localhost:7688,localhost:7689/?noSsl&routing:region=europe&country=it"));
-		assertTrue(driver.acceptsURL("jdbc:neo4j:neo4j://localhost:7687/?noSsl&routing:region=europe&country=it&routing:servers=localhost:7688,localhost:7689"));
-	}
-
 	@Test public void shoulNotAcceptURL() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, SQLException {
 		Neo4jDriver driver = new BoltRoutingNeo4jDriver();
 		assertFalse(driver.acceptsURL("jdbc:neo4j:http://localhost:7687"));
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriverUrlTest.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriverUrlTest.java
new file mode 100644
index 00000000..7607ee6c
--- /dev/null
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/boltrouting/BoltRoutingNeo4jDriverUrlTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 23/02/16
+ */
+package org.neo4j.jdbc.boltrouting;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.mockito.Mockito;
+import org.neo4j.driver.AuthTokens;
+import org.neo4j.driver.Config;
+import org.neo4j.driver.GraphDatabase;
+import org.neo4j.jdbc.Neo4jDriver;
+import org.neo4j.jdbc.bolt.utils.Mocker;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.modules.junit4.PowerMockRunnerDelegate;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+
+/**
+ * @author AgileLARUS
+ * @since 3.0.0
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({GraphDatabase.class, Config.TrustStrategy.class, AuthTokens.class})
+@PowerMockRunnerDelegate(Parameterized.class)
+public class BoltRoutingNeo4jDriverUrlTest {
+
+	@Rule public ExpectedException expectedEx = ExpectedException.none();
+
+	@Parameterized.Parameter
+	public String completeValidUrl;
+
+	private static org.neo4j.driver.Driver mockedDriver;
+
+	@Parameterized.Parameters
+	public static Iterable<? extends Object> data() {
+		return Arrays.asList("jdbc:neo4j:neo4j://test", "jdbc:neo4j:neo4j+s://test", "jdbc:neo4j:neo4j+ssc://test");
+	}
+
+	@BeforeClass
+	public static void initialize() {
+		mockedDriver = Mocker.mockDriver();
+	}
+
+	@Before
+	public void prepare() {
+		BoltRoutingNeo4jDriver.clearCache();
+	}
+
+	private String getBoltUrl() {
+		return completeValidUrl.replace("jdbc:neo4j:", "");
+	}
+
+	private String getPrefix() {
+		return completeValidUrl.split("://")[0];
+	}
+
+	@Test public void shouldConnectCreateConnection() throws SQLException, URISyntaxException {
+		PowerMockito.mockStatic(GraphDatabase.class);
+		Mockito.when(GraphDatabase.routingDriver(Mockito.eq(Arrays.asList(new URI(getBoltUrl()))), Mockito.eq(AuthTokens.none()), any(Config.class))).thenReturn(mockedDriver);
+
+		Neo4jDriver driver = new BoltRoutingNeo4jDriver();
+		Connection connection = driver.connect(completeValidUrl, null);
+		assertNotNull(connection);
+	}
+
+	@Test public void shouldAcceptURL() throws SQLException {
+		Neo4jDriver driver = new BoltRoutingNeo4jDriver();
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://192.168.0.1:7687"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://192.168.0.1:7687/"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687?routing:policy=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687?routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/?routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687?noSsl&routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/?noSsl&routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687,localhost:7688,localhost:7689/?noSsl&routing:region=europe&country=it"));
+		assertTrue(driver.acceptsURL(getPrefix() + "://localhost:7687/?noSsl&routing:region=europe&country=it&routing:servers=localhost:7688,localhost:7689"));
+	}
+
+
+}
diff --git a/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/utils/RetryExceptionPredicateTest.java b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/utils/RetryExceptionPredicateTest.java
new file mode 100644
index 00000000..aebb3b9c
--- /dev/null
+++ b/neo4j-jdbc-bolt/src/test/java/org/neo4j/jdbc/utils/RetryExceptionPredicateTest.java
@@ -0,0 +1,28 @@
+package org.neo4j.jdbc.utils;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.Invocation;
+import org.neo4j.driver.Result;
+import org.neo4j.driver.Transaction;
+import org.neo4j.driver.exceptions.TransientException;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class RetryExceptionPredicateTest {
+
+    @Test
+    public void shouldRetry() throws Exception {
+        final Transaction mockedTransaction = Mockito.mock(Transaction.class);
+        final Result mockedResult = Mockito.mock(Result.class);
+        Mockito.when(mockedTransaction.run(Mockito.anyString(), Mockito.anyMap()))
+                .thenThrow(new TransientException("code.allowed.to.retry", ""))
+                .thenReturn(mockedResult);
+        final Result result = BoltNeo4jUtils.runTransactionWithRetries(mockedTransaction, "", Collections.emptyMap());
+        final Collection<Invocation> invocations = Mockito.mockingDetails(mockedTransaction).getInvocations();
+        Assert.assertEquals(mockedResult, result);
+        Assert.assertEquals(2, invocations.size());
+    }
+}