diff --git a/pom.xml b/pom.xml index 9428c68cb..129016b7b 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.arangodb arangodb-java-driver - 6.14.0 + 6.15.0-SNAPSHOT 2016 jar diff --git a/src/main/java/com/arangodb/ArangoDBException.java b/src/main/java/com/arangodb/ArangoDBException.java index 7e5c45886..6ea9ce8c8 100644 --- a/src/main/java/com/arangodb/ArangoDBException.java +++ b/src/main/java/com/arangodb/ArangoDBException.java @@ -56,6 +56,12 @@ public ArangoDBException(final Throwable cause) { this.responseCode = null; } + public ArangoDBException(final String message, final Throwable cause) { + super(message, cause); + this.entity = null; + this.responseCode = null; + } + /** * @return ArangoDB error message */ diff --git a/src/main/java/com/arangodb/ArangoDBMultipleException.java b/src/main/java/com/arangodb/ArangoDBMultipleException.java new file mode 100644 index 000000000..4181cb95d --- /dev/null +++ b/src/main/java/com/arangodb/ArangoDBMultipleException.java @@ -0,0 +1,44 @@ +package com.arangodb; + +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; + +public class ArangoDBMultipleException extends RuntimeException { + + private final List exceptions; + + public ArangoDBMultipleException(List exceptions) { + super(); + this.exceptions = exceptions; + } + + public List getExceptions() { + return exceptions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ArangoDBMultipleException that = (ArangoDBMultipleException) o; + return Objects.equals(exceptions, that.exceptions); + } + + @Override + public int hashCode() { + return Objects.hash(exceptions); + } + + @Override + public String toString() { + StringJoiner joiner = new StringJoiner("\n\t", "ArangoDBMultipleException{\n\t", "\n}"); + for (Throwable t : exceptions) { + StringJoiner tJoiner = new StringJoiner("\n\t\t", "\n\t\t", ""); + for (StackTraceElement stackTraceElement : t.getStackTrace()) + tJoiner.add("at " + stackTraceElement); + joiner.add(t + tJoiner.toString()); + } + return joiner.toString(); + } +} diff --git a/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java b/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java index bd3540f04..3a1b3f1b7 100644 --- a/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java +++ b/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java @@ -85,7 +85,7 @@ protected CompletableFuture execute(final Request request, final VstCo final String location = e.getLocation(); final HostDescription redirectHost = HostUtils.createFromLocation(location); hostHandler.closeCurrentOnError(); - hostHandler.fail(); + hostHandler.fail(e); execute(request, new HostHandle().setHost(redirectHost), attemptCount + 1) .whenComplete((v, err) -> { if (v != null) { diff --git a/src/main/java/com/arangodb/internal/http/HttpCommunication.java b/src/main/java/com/arangodb/internal/http/HttpCommunication.java index 558b058db..c40106b8a 100644 --- a/src/main/java/com/arangodb/internal/http/HttpCommunication.java +++ b/src/main/java/com/arangodb/internal/http/HttpCommunication.java @@ -32,7 +32,6 @@ import java.io.Closeable; import java.io.IOException; -import java.net.SocketException; /** * @author Mark Vollmary @@ -71,11 +70,11 @@ public void close() throws IOException { hostHandler.close(); } - public Response execute(final Request request, final HostHandle hostHandle) throws ArangoDBException, IOException { + public Response execute(final Request request, final HostHandle hostHandle) throws ArangoDBException { return execute(request, hostHandle, 0); } - private Response execute(final Request request, final HostHandle hostHandle, final int attemptCount) throws ArangoDBException, IOException { + private Response execute(final Request request, final HostHandle hostHandle, final int attemptCount) throws ArangoDBException { final AccessType accessType = RequestUtils.determineAccessType(request); Host host = hostHandler.get(hostHandle, accessType); try { @@ -86,19 +85,20 @@ private Response execute(final Request request, final HostHandle hostHandle, fin hostHandler.success(); hostHandler.confirm(); return response; - } catch (final SocketException se) { - hostHandler.fail(); + } catch (final IOException e) { + hostHandler.fail(e); if (hostHandle != null && hostHandle.getHost() != null) { hostHandle.setHost(null); } final Host failedHost = host; host = hostHandler.get(hostHandle, accessType); if (host != null) { - LOGGER.warn(String.format("Could not connect to %s", failedHost.getDescription()), se); + LOGGER.warn(String.format("Could not connect to %s", failedHost.getDescription()), e); LOGGER.warn(String.format("Could not connect to %s. Try connecting to %s", failedHost.getDescription(), host.getDescription())); } else { - throw se; + LOGGER.error(e.getMessage(), e); + throw new ArangoDBException(e); } } } @@ -107,7 +107,7 @@ private Response execute(final Request request, final HostHandle hostHandle, fin final String location = ((ArangoDBRedirectException) e).getLocation(); final HostDescription redirectHost = HostUtils.createFromLocation(location); hostHandler.closeCurrentOnError(); - hostHandler.fail(); + hostHandler.fail(e); return execute(request, new HostHandle().setHost(redirectHost), attemptCount + 1); } else { throw e; diff --git a/src/main/java/com/arangodb/internal/http/HttpProtocol.java b/src/main/java/com/arangodb/internal/http/HttpProtocol.java index d946a5306..7997b245e 100644 --- a/src/main/java/com/arangodb/internal/http/HttpProtocol.java +++ b/src/main/java/com/arangodb/internal/http/HttpProtocol.java @@ -42,11 +42,7 @@ public HttpProtocol(final HttpCommunication httpCommunitaction) { @Override public Response execute(final Request request, final HostHandle hostHandle) throws ArangoDBException { - try { - return httpCommunitaction.execute(request, hostHandle); - } catch (final IOException e) { - throw new ArangoDBException(e); - } + return httpCommunitaction.execute(request, hostHandle); } @Override diff --git a/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java b/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java index 47feda2f6..93039224d 100644 --- a/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java @@ -56,8 +56,8 @@ public void success() { } @Override - public void fail() { - determineHostHandler().fail(); + public void fail(Exception exception) { + determineHostHandler().fail(exception); } @Override diff --git a/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java b/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java index c440f22f0..d88ac21ec 100644 --- a/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java @@ -21,7 +21,9 @@ package com.arangodb.internal.net; import com.arangodb.ArangoDBException; +import com.arangodb.ArangoDBMultipleException; +import java.util.ArrayList; import java.util.List; /** @@ -33,12 +35,14 @@ public class FallbackHostHandler implements HostHandler { private Host current; private Host lastSuccess; private int iterations; + private final List lastFailExceptions; private boolean firstOpened; private HostSet hosts; public FallbackHostHandler(final HostResolver resolver) { this.resolver = resolver; - iterations = 0; + lastFailExceptions = new ArrayList<>(); + reset(); hosts = resolver.resolve(true, false); current = lastSuccess = hosts.getHostsList().get(0); firstOpened = true; @@ -49,19 +53,21 @@ public Host get(final HostHandle hostHandle, AccessType accessType) { if (current != lastSuccess || iterations < 3) { return current; } else { + ArangoDBException e = new ArangoDBException("Cannot contact any host!", + new ArangoDBMultipleException(new ArrayList<>(lastFailExceptions))); reset(); - throw new ArangoDBException("Cannot contact any host!"); + throw e; } } @Override public void success() { lastSuccess = current; - iterations = 0; + reset(); } @Override - public void fail() { + public void fail(Exception exception) { hosts = resolver.resolve(false, false); final List hostList = hosts.getHostsList(); final int index = hostList.indexOf(current) + 1; @@ -70,11 +76,13 @@ public void fail() { if (!inBound) { iterations++; } + lastFailExceptions.add(exception); } @Override public void reset() { iterations = 0; + lastFailExceptions.clear(); } @Override diff --git a/src/main/java/com/arangodb/internal/net/HostHandler.java b/src/main/java/com/arangodb/internal/net/HostHandler.java index dacd801f7..b432ca81e 100644 --- a/src/main/java/com/arangodb/internal/net/HostHandler.java +++ b/src/main/java/com/arangodb/internal/net/HostHandler.java @@ -31,7 +31,7 @@ public interface HostHandler { void success(); - void fail(); + void fail(Exception exception); void reset(); diff --git a/src/main/java/com/arangodb/internal/net/RandomHostHandler.java b/src/main/java/com/arangodb/internal/net/RandomHostHandler.java index a52147e55..a3ee4cac7 100644 --- a/src/main/java/com/arangodb/internal/net/RandomHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/RandomHostHandler.java @@ -54,8 +54,8 @@ public void success() { } @Override - public void fail() { - fallback.fail(); + public void fail(Exception exception) { + fallback.fail(exception); current = fallback.get(null, null); } diff --git a/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java b/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java index 20389245b..e4e653ff9 100644 --- a/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java @@ -21,6 +21,10 @@ package com.arangodb.internal.net; import com.arangodb.ArangoDBException; +import com.arangodb.ArangoDBMultipleException; + +import java.util.ArrayList; +import java.util.List; /** * @author Mark Vollmary @@ -30,15 +34,17 @@ public class RoundRobinHostHandler implements HostHandler { private final HostResolver resolver; private int current; private int fails; + private final List lastFailExceptions; private Host currentHost; private HostSet hosts; public RoundRobinHostHandler(final HostResolver resolver) { super(); this.resolver = resolver; + lastFailExceptions = new ArrayList<>(); hosts = resolver.resolve(true, false); current = 0; - fails = 0; + reset(); } @Override @@ -47,8 +53,10 @@ public Host get(final HostHandle hostHandle, AccessType accessType) { final int size = hosts.getHostsList().size(); if (fails > size) { + ArangoDBException e = new ArangoDBException("Cannot contact any host!", + new ArangoDBMultipleException(new ArrayList<>(lastFailExceptions))); reset(); - throw new ArangoDBException("Cannot contact any host!"); + throw e; } final int index = (current++) % size; @@ -72,17 +80,19 @@ public Host get(final HostHandle hostHandle, AccessType accessType) { @Override public void success() { - fails = 0; + reset(); } @Override - public void fail() { + public void fail(Exception exception) { fails++; + lastFailExceptions.add(exception); } @Override public void reset() { fails = 0; + lastFailExceptions.clear(); } @Override diff --git a/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java b/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java index 854ca0f1c..92187e9ba 100644 --- a/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java +++ b/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java @@ -95,13 +95,13 @@ protected synchronized C connect(final HostHandle hostHandle, final AccessType a hostHandler.confirm(); if (!connection.isOpen()) { // see https://github.com/arangodb/arangodb-java-driver/issues/384 - hostHandler.fail(); + hostHandler.fail(new IOException("The connection is closed.")); host = hostHandler.get(hostHandle, accessType); continue; } return connection; } catch (final IOException e) { - hostHandler.fail(); + hostHandler.fail(e); if (hostHandle != null && hostHandle.getHost() != null) { hostHandle.setHost(null); } diff --git a/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java b/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java index 3691f65b0..f433194bf 100644 --- a/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java +++ b/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java @@ -143,7 +143,7 @@ protected Response execute(final Request request, final VstConnectionSync connec final String location = e.getLocation(); final HostDescription redirectHost = HostUtils.createFromLocation(location); hostHandler.closeCurrentOnError(); - hostHandler.fail(); + hostHandler.fail(e); return execute(request, new HostHandle().setHost(redirectHost), attemptCount + 1); } } diff --git a/src/test/java/com/arangodb/ArangoSslTest.java b/src/test/java/com/arangodb/ArangoSslTest.java index 77d1f7409..1c8af8074 100644 --- a/src/test/java/com/arangodb/ArangoSslTest.java +++ b/src/test/java/com/arangodb/ArangoSslTest.java @@ -29,10 +29,10 @@ import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.TrustManagerFactory; import java.security.KeyStore; +import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.fail; /** @@ -84,7 +84,9 @@ public void connectWithoutValidSslContext() { arangoDB.getVersion(); fail("this should fail"); } catch (final ArangoDBException ex) { - assertThat(ex.getCause() instanceof SSLHandshakeException, is(true)); + assertThat(ex.getCause(), is(instanceOf(ArangoDBMultipleException.class))); + List exceptions = ((ArangoDBMultipleException) ex.getCause()).getExceptions(); + exceptions.forEach(e -> assertThat(e, is(instanceOf(SSLHandshakeException.class)))); } } diff --git a/src/test/java/com/arangodb/internal/HostHandlerTest.java b/src/test/java/com/arangodb/internal/HostHandlerTest.java index 077634ed8..7a4d22db0 100644 --- a/src/test/java/com/arangodb/internal/HostHandlerTest.java +++ b/src/test/java/com/arangodb/internal/HostHandlerTest.java @@ -21,10 +21,13 @@ package com.arangodb.internal; import com.arangodb.ArangoDBException; +import com.arangodb.ArangoDBMultipleException; import com.arangodb.internal.net.*; import com.arangodb.util.ArangoSerialization; import org.junit.Test; +import java.util.List; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -76,10 +79,10 @@ public void init(ArangoExecutorSync executor, ArangoSerialization arangoSerializ }; @Test - public void fallbachHostHandlerSingleHost() { + public void fallbackHostHandlerSingleHost() { final HostHandler handler = new FallbackHostHandler(SINGLE_HOST); assertThat(handler.get(null, null), is(HOST_0)); - handler.fail(); + handler.fail(new RuntimeException()); assertThat(handler.get(null, null), is(HOST_0)); } @@ -88,19 +91,27 @@ public void fallbackHostHandlerMultipleHosts() { final HostHandler handler = new FallbackHostHandler(MULTIPLE_HOSTS); for (int i = 0; i < 3; i++) { assertThat(handler.get(null, null), is(HOST_0)); - handler.fail(); + handler.fail(new RuntimeException("HOST_0 failed")); assertThat(handler.get(null, null), is(HOST_1)); - handler.fail(); + handler.fail(new RuntimeException("HOST_1 failed")); assertThat(handler.get(null, null), is(HOST_2)); + handler.fail(new RuntimeException("HOST_2 failed")); if (i < 2) { - handler.fail(); assertThat(handler.get(null, null), is(HOST_0)); } else { - handler.fail(); try { handler.get(null, null); fail(); - } catch (ArangoDBException ignored) { + } catch (ArangoDBException e) { + assertThat(e.getCause(), is(notNullValue())); + assertThat(e.getCause(), is(instanceOf(ArangoDBMultipleException.class))); + List exceptions = ((ArangoDBMultipleException) e.getCause()).getExceptions(); + assertThat(exceptions.get(0), is(instanceOf(RuntimeException.class))); + assertThat(exceptions.get(0).getMessage(), is("HOST_0 failed")); + assertThat(exceptions.get(1), is(instanceOf(RuntimeException.class))); + assertThat(exceptions.get(1).getMessage(), is("HOST_1 failed")); + assertThat(exceptions.get(2), is(instanceOf(RuntimeException.class))); + assertThat(exceptions.get(2).getMessage(), is("HOST_2 failed")); } } } @@ -110,7 +121,7 @@ public void fallbackHostHandlerMultipleHosts() { public void randomHostHandlerSingleHost() { final HostHandler handler = new RandomHostHandler(SINGLE_HOST, new FallbackHostHandler(SINGLE_HOST)); assertThat(handler.get(null, null), is(HOST_0)); - handler.fail(); + handler.fail(new RuntimeException()); assertThat(handler.get(null, null), is(HOST_0)); } @@ -120,7 +131,7 @@ public void randomHostHandlerMultipeHosts() { final Host pick0 = handler.get(null, null); assertThat(pick0, anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); - handler.fail(); + handler.fail(new RuntimeException()); final Host pick1 = handler.get(null, null); assertThat(pick1, anyOf(is(HOST_0), is(HOST_1), is(HOST_2))); @@ -135,7 +146,7 @@ public void randomHostHandlerMultipeHosts() { public void roundRobinHostHandlerSingleHost() { final HostHandler handler = new RoundRobinHostHandler(SINGLE_HOST); assertThat(handler.get(null, null), is(HOST_0)); - handler.fail(); + handler.fail(new RuntimeException()); assertThat(handler.get(null, null), is(HOST_0)); }