diff --git a/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java index 23c7ceb16e2..0cb2920dce1 100644 --- a/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java +++ b/spring-core/src/main/java/org/springframework/core/NestedCheckedException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,13 +80,7 @@ public abstract class NestedCheckedException extends Exception { * @return the innermost exception, or {@code null} if none */ public Throwable getRootCause() { - Throwable rootCause = null; - Throwable cause = getCause(); - while (cause != null && cause != rootCause) { - rootCause = cause; - cause = cause.getCause(); - } - return rootCause; + return NestedExceptionUtils.getRootCause(this); } /** diff --git a/spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java b/spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java index 8b66390f469..e7c11aed755 100644 --- a/spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java +++ b/spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,17 +39,48 @@ public abstract class NestedExceptionUtils { * @return the full exception message */ public static String buildMessage(String message, Throwable cause) { - if (cause != null) { - StringBuilder sb = new StringBuilder(); - if (message != null) { - sb.append(message).append("; "); - } - sb.append("nested exception is ").append(cause); - return sb.toString(); - } - else { + if (cause == null) { return message; } + StringBuilder sb = new StringBuilder(64); + if (message != null) { + sb.append(message).append("; "); + } + sb.append("nested exception is ").append(cause); + return sb.toString(); + } + + /** + * Retrieve the innermost cause of the given exception, if any. + * @param original the original exception to introspect + * @return the innermost exception, or {@code null} if none + * @since 4.3.9 + */ + public static Throwable getRootCause(Throwable original) { + if (original == null) { + return null; + } + Throwable rootCause = null; + Throwable cause = original.getCause(); + while (cause != null && cause != rootCause) { + rootCause = cause; + cause = cause.getCause(); + } + return rootCause; + } + + /** + * Retrieve the most specific cause of the given exception, that is, + * either the innermost cause (root cause) or the exception itself. + *
Differs from {@link #getRootCause} in that it falls back
+	 * to the original exception if there is no root cause.
+	 * @param original the original exception to introspect
+	 * @return the most specific cause (never {@code null})
+	 * @since 4.3.9
+	 */
+	public static Throwable getMostSpecificCause(Throwable original) {
+		Throwable rootCause = getRootCause(original);
+		return (rootCause != null ? rootCause : original);
 	}
 
 }
diff --git a/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
index 8d20d81f884..6e8843f0f8c 100644
--- a/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
+++ b/spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2012 the original author or authors.
+ * Copyright 2002-2017 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -81,13 +81,7 @@ public abstract class NestedRuntimeException extends RuntimeException {
 	 * @since 2.0
 	 */
 	public Throwable getRootCause() {
-		Throwable rootCause = null;
-		Throwable cause = getCause();
-		while (cause != null && cause != rootCause) {
-			rootCause = cause;
-			cause = cause.getCause();
-		}
-		return rootCause;
+		return NestedExceptionUtils.getRootCause(this);
 	}
 
 	/**
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java
index c8a45096e38..925f36b67fd 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java
@@ -19,7 +19,6 @@ package org.springframework.web.socket.sockjs.transport.session;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -31,7 +30,7 @@ import java.util.concurrent.ScheduledFuture;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import org.springframework.core.NestedCheckedException;
+import org.springframework.core.NestedExceptionUtils;
 import org.springframework.util.Assert;
 import org.springframework.web.socket.CloseStatus;
 import org.springframework.web.socket.TextMessage;
@@ -70,25 +69,22 @@ public abstract class AbstractSockJsSession implements SockJsSession {
 	public static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
 			"org.springframework.web.socket.sockjs.DisconnectedClient";
 
+	/**
+	 * Tomcat: ClientAbortException or EOFException
+	 * Jetty: EofException
+	 * WildFly, GlassFish: java.io.IOException "Broken pipe" (already covered)
+	 * @see #indicatesDisconnectedClient(Throwable)
+	 */
+	private static final Set