Bug 567544 Handle DockerRequestException in DockerException

The code extracting the docker server's return message only worked
correctly if the DockerRequestException was specifically handled in the
catch block by setting getResponseBody() as message. By recursively
walking through the causes, this will now work without specific
handling.

Change-Id: I7f37b248b64ac8e826a4a7a1bda62857439de06e
Signed-off-by: Moritz 'Morty' Strübe <moritz.struebe@mathema.de>
Reviewed-on: https://git.eclipse.org/r/c/linuxtools/org.eclipse.linuxtools/+/170201
Tested-by: Linux Tools Bot <linuxtools-bot@eclipse.org>
Reviewed-by: Jeff Johnston <jjohnstn@redhat.com>
diff --git a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/docker/core/DockerException.java b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/docker/core/DockerException.java
index 57f7ded..b28a911 100644
--- a/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/docker/core/DockerException.java
+++ b/containers/org.eclipse.linuxtools.docker.core/src/org/eclipse/linuxtools/docker/core/DockerException.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2014, 2018 Red Hat.
+ * Copyright (c) 2014, 2020 Red Hat and others.
  * 
  * This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License 2.0
@@ -12,38 +12,60 @@
  *******************************************************************************/
 package org.eclipse.linuxtools.docker.core;
 
+import org.mandas.docker.client.exceptions.DockerRequestException;
+
 public class DockerException extends Exception {
 
 	private static final long serialVersionUID = 1L;
 
-	private String JSON_MESSAGE_PREFIX = "{\"message\":\""; //$NON-NLS-1$
-	private String JSON_MESSAGE_SUFFIX = "\"}"; //$NON-NLS-1$
+	private static String JSON_MESSAGE_PREFIX = "{\"message\":\""; //$NON-NLS-1$
+	private static String JSON_MESSAGE_SUFFIX = "\"}"; //$NON-NLS-1$
 
 	public DockerException(final String message) {
 		super(message);
 	}
 
 	public DockerException(final String message, final Throwable cause) {
-		super(message, cause);
+		super(calculateMessage(message, cause), cause);
 	}
 
 	public DockerException(final Throwable cause) {
-		super(cause);
+		super(calculateMessage(null, cause), cause);
 	}
 
-	@Override
-	public String getMessage() {
-		String s = super.getMessage();
-		// Bug 499917 - temporarily massage any message we get back from docker
-		// client if it is in JSON format. This code can be removed once docker
-		// client has fixed itself to work with error messages from docker
-		// 1.12.0 and beyond.
-		if (s.startsWith(JSON_MESSAGE_PREFIX)) {
-			s = s.substring(JSON_MESSAGE_PREFIX.length());
-			s = s.replaceAll(JSON_MESSAGE_SUFFIX, ""); //$NON-NLS-1$
-			return s;
+	static private String calculateMessage(final String message, final Throwable cause) {
+		Throwable dre = cause;
+		// Search for DockerRequestException
+		while (dre != null && !(dre instanceof DockerRequestException)) {
+			dre = dre.getCause();
 		}
-		return super.getMessage();
+
+		// Handle DockerRequestException
+		if (dre != null) {
+			// Bug 499917 - temporarily massage any message we get back from
+			// docker client if it is in JSON format. This code can be removed
+			// once docker client has fixed itself to work with error messages
+			// from docker 1.12.0 and beyond.
+			DockerRequestException re = (DockerRequestException) dre;
+			String s = re.getResponseBody();
+			if (s.startsWith(JSON_MESSAGE_PREFIX)) {
+				s = s.substring(JSON_MESSAGE_PREFIX.length());
+				s = s.replaceAll(JSON_MESSAGE_SUFFIX, ""); //$NON-NLS-1$
+				if (message != null) {
+					return message + "; " + s;
+				} else {
+					return s;
+				}
+			}
+		}
+
+		// As it's not possible to select the super-constructor Throwable's
+		// behavior must be simulated
+		if ((message == null) && (cause != null)) {
+			return cause.toString();
+		} else {
+			return message;
+		}
 	}
 
 }
diff --git a/containers/org.eclipse.linuxtools.docker.ui.tests/src/org/eclipse/linuxtools/docker/core/DockerExceptionTest.java b/containers/org.eclipse.linuxtools.docker.ui.tests/src/org/eclipse/linuxtools/docker/core/DockerExceptionTest.java
index 4593bcb..9672d4f 100644
--- a/containers/org.eclipse.linuxtools.docker.ui.tests/src/org/eclipse/linuxtools/docker/core/DockerExceptionTest.java
+++ b/containers/org.eclipse.linuxtools.docker.ui.tests/src/org/eclipse/linuxtools/docker/core/DockerExceptionTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016,2018 Red Hat.
+ * Copyright (c) 2016, 2020 Red Hat and others.
  *
  * This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License 2.0
@@ -15,11 +15,15 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
+import org.mandas.docker.client.exceptions.DockerRequestException;
 
 /**
  * Testing the {@link DockerException} class
@@ -28,14 +32,68 @@
 public class DockerExceptionTest {
 
 	@Parameters
-	public static Object[][] getData() {
+	public static Object[][] getData() throws URISyntaxException {
 		final Object[][] data = new Object[][] {
+				// 0: Normal exceptions
 				new Object[] { new DockerException("this is an error"), "this is an error" },
+				// 1:
 				new Object[] { new DockerException("error with message: 232"), "error with message: 232" },
+				// 2: Use passed message or message from original exception - Behave like
+				// Throwable
+				new Object[] { new DockerException(null, new RuntimeException("This is a test")),
+						"java.lang.RuntimeException: This is a test" },
+				// 3:
+				new Object[] { new DockerException("First", new RuntimeException("This is a test")), "First" },
+				// 4: Do not parse passed message
 				new Object[] {
 						new DockerException(
 								"{\"message\":\"invalid reference format: repository name must be lowercase\"}"),
-						"invalid reference format: repository name must be lowercase" }, };
+						"{\"message\":\"invalid reference format: repository name must be lowercase\"}" },
+				// 5: Decode DockerRequestException
+				new Object[] { new DockerException(null, new DockerRequestException("m", new URI("http://none"), 404,
+						"{\"message\":\"invalid reference format: repository name must be lowercase\"}", null)),
+						"invalid reference format: repository name must be lowercase"
+				},
+				// 6: DockerRequestException and prefix message
+				new Object[] { new DockerException("Additional info", new DockerRequestException("m", new URI("http://none"), 404,
+						"{\"message\":\"invalid reference format: repository name must be lowercase\"}", null)),
+						"Additional info; invalid reference format: repository name must be lowercase"
+				},
+				// 7: DockerRequestException Without Json-message - use toString
+				new Object[] {
+						new DockerException(
+								new DockerRequestException("Hello", new URI("http://none"), 404, "Response", null)),
+						"org.mandas.docker.client.exceptions.DockerRequestException: Request error: Hello http://none: 404, body: Response" },
+				// 8:
+				new Object[] {
+						new DockerException("Additional info",
+								new DockerRequestException("Hello", new URI("http://none"), 404, "Response", null)),
+						"Additional info" },
+				// 9: Search DockerRequestException
+				new Object[] { new DockerException(null,
+						new RuntimeException(new DockerRequestException("m", new URI("http://none"), 404,
+								"{\"message\":\"invalid reference format: repository name must be lowercase\"}",
+								null))),
+						"invalid reference format: repository name must be lowercase" },
+				// 10: Wrapped in exception with message
+				new Object[] { new DockerException(null, new RuntimeException("Hello", new DockerRequestException("m",
+						new URI("http://none"), 404,
+								"{\"message\":\"invalid reference format: repository name must be lowercase\"}",
+								null))),
+						"invalid reference format: repository name must be lowercase" },
+				// 11: Wrapped twice
+				new Object[] {
+						new DockerException(null,
+								new RuntimeException(new RuntimeException(new DockerRequestException("m",
+										new URI("http://none"), 404,
+										"{\"message\":\"invalid reference format: repository name must be lowercase\"}",
+										null)))),
+						"invalid reference format: repository name must be lowercase" },
+				// 12: Wrapped without message
+				new Object[] { new DockerException(null, new RuntimeException("Hello",
+						new DockerRequestException("m", new URI("http://none"), 404, "Do not show this", null))),
+						"java.lang.RuntimeException: Hello" },
+		};
 		return data;
 	}