Skip to content

Commit 2d1b551

Browse files
committed
Common root cause introspection algorithm in NestedExceptionUtils
Issue: SPR-15510 (cherry picked from commit 9d8e9cf)
1 parent aa8cf19 commit 2d1b551

File tree

4 files changed

+70
-54
lines changed

4 files changed

+70
-54
lines changed

spring-core/src/main/java/org/springframework/core/NestedCheckedException.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -80,13 +80,7 @@ public String getMessage() {
8080
* @return the innermost exception, or {@code null} if none
8181
*/
8282
public Throwable getRootCause() {
83-
Throwable rootCause = null;
84-
Throwable cause = getCause();
85-
while (cause != null && cause != rootCause) {
86-
rootCause = cause;
87-
cause = cause.getCause();
88-
}
89-
return rootCause;
83+
return NestedExceptionUtils.getRootCause(this);
9084
}
9185

9286
/**

spring-core/src/main/java/org/springframework/core/NestedExceptionUtils.java

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,17 +39,48 @@ public abstract class NestedExceptionUtils {
3939
* @return the full exception message
4040
*/
4141
public static String buildMessage(String message, Throwable cause) {
42-
if (cause != null) {
43-
StringBuilder sb = new StringBuilder();
44-
if (message != null) {
45-
sb.append(message).append("; ");
46-
}
47-
sb.append("nested exception is ").append(cause);
48-
return sb.toString();
49-
}
50-
else {
42+
if (cause == null) {
5143
return message;
5244
}
45+
StringBuilder sb = new StringBuilder(64);
46+
if (message != null) {
47+
sb.append(message).append("; ");
48+
}
49+
sb.append("nested exception is ").append(cause);
50+
return sb.toString();
51+
}
52+
53+
/**
54+
* Retrieve the innermost cause of the given exception, if any.
55+
* @param original the original exception to introspect
56+
* @return the innermost exception, or {@code null} if none
57+
* @since 4.3.9
58+
*/
59+
public static Throwable getRootCause(Throwable original) {
60+
if (original == null) {
61+
return null;
62+
}
63+
Throwable rootCause = null;
64+
Throwable cause = original.getCause();
65+
while (cause != null && cause != rootCause) {
66+
rootCause = cause;
67+
cause = cause.getCause();
68+
}
69+
return rootCause;
70+
}
71+
72+
/**
73+
* Retrieve the most specific cause of the given exception, that is,
74+
* either the innermost cause (root cause) or the exception itself.
75+
* <p>Differs from {@link #getRootCause} in that it falls back
76+
* to the original exception if there is no root cause.
77+
* @param original the original exception to introspect
78+
* @return the most specific cause (never {@code null})
79+
* @since 4.3.9
80+
*/
81+
public static Throwable getMostSpecificCause(Throwable original) {
82+
Throwable rootCause = getRootCause(original);
83+
return (rootCause != null ? rootCause : original);
5384
}
5485

5586
}

spring-core/src/main/java/org/springframework/core/NestedRuntimeException.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -81,13 +81,7 @@ public String getMessage() {
8181
* @since 2.0
8282
*/
8383
public Throwable getRootCause() {
84-
Throwable rootCause = null;
85-
Throwable cause = getCause();
86-
while (cause != null && cause != rootCause) {
87-
rootCause = cause;
88-
cause = cause.getCause();
89-
}
90-
return rootCause;
84+
return NestedExceptionUtils.getRootCause(this);
9185
}
9286

9387
/**

spring-websocket/src/main/java/org/springframework/web/socket/sockjs/transport/session/AbstractSockJsSession.java

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.io.IOException;
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
22-
import java.util.Collections;
2322
import java.util.Date;
2423
import java.util.HashSet;
2524
import java.util.List;
@@ -31,7 +30,7 @@
3130
import org.apache.commons.logging.Log;
3231
import org.apache.commons.logging.LogFactory;
3332

34-
import org.springframework.core.NestedCheckedException;
33+
import org.springframework.core.NestedExceptionUtils;
3534
import org.springframework.util.Assert;
3635
import org.springframework.web.socket.CloseStatus;
3736
import org.springframework.web.socket.TextMessage;
@@ -70,25 +69,22 @@ private enum State {NEW, OPEN, CLOSED}
7069
public static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
7170
"org.springframework.web.socket.sockjs.DisconnectedClient";
7271

72+
/**
73+
* Tomcat: ClientAbortException or EOFException
74+
* Jetty: EofException
75+
* WildFly, GlassFish: java.io.IOException "Broken pipe" (already covered)
76+
* @see #indicatesDisconnectedClient(Throwable)
77+
*/
78+
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS =
79+
new HashSet<String>(Arrays.asList("ClientAbortException", "EOFException", "EofException"));
80+
81+
7382
/**
7483
* Separate logger to use on network IO failure after a client has gone away.
7584
* @see #DISCONNECTED_CLIENT_LOG_CATEGORY
7685
*/
7786
protected static final Log disconnectedClientLogger = LogFactory.getLog(DISCONNECTED_CLIENT_LOG_CATEGORY);
7887

79-
80-
private static final Set<String> disconnectedClientExceptions;
81-
82-
static {
83-
Set<String> set = new HashSet<String>(4);
84-
set.add("ClientAbortException"); // Tomcat
85-
set.add("EOFException"); // Tomcat
86-
set.add("EofException"); // Jetty
87-
// java.io.IOException "Broken pipe" on WildFly, Glassfish (already covered)
88-
disconnectedClientExceptions = Collections.unmodifiableSet(set);
89-
}
90-
91-
9288
protected final Log logger = LogFactory.getLog(getClass());
9389

9490
protected final Object responseLock = new Object();
@@ -340,28 +336,28 @@ protected void writeFrame(SockJsFrame frame) throws SockJsTransportFailureExcept
340336
}
341337
}
342338

343-
private void logWriteFrameFailure(Throwable failure) {
344-
@SuppressWarnings("serial")
345-
NestedCheckedException nestedException = new NestedCheckedException("", failure) {};
346-
347-
if ("Broken pipe".equalsIgnoreCase(nestedException.getMostSpecificCause().getMessage()) ||
348-
disconnectedClientExceptions.contains(failure.getClass().getSimpleName())) {
339+
protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException;
349340

341+
private void logWriteFrameFailure(Throwable ex) {
342+
if (indicatesDisconnectedClient(ex)) {
350343
if (disconnectedClientLogger.isTraceEnabled()) {
351-
disconnectedClientLogger.trace("Looks like the client has gone away", failure);
344+
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
352345
}
353346
else if (disconnectedClientLogger.isDebugEnabled()) {
354-
disconnectedClientLogger.debug("Looks like the client has gone away: " +
355-
nestedException.getMessage() + " (For full stack trace, set the '" +
356-
DISCONNECTED_CLIENT_LOG_CATEGORY + "' log category to TRACE level)");
347+
disconnectedClientLogger.debug("Looks like the client has gone away: " + ex +
348+
" (For a full stack trace, set the log category '" + DISCONNECTED_CLIENT_LOG_CATEGORY +
349+
"' to TRACE level.)");
357350
}
358351
}
359352
else {
360-
logger.debug("Terminating connection after failure to send message to client", failure);
353+
logger.debug("Terminating connection after failure to send message to client", ex);
361354
}
362355
}
363356

364-
protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException;
357+
private boolean indicatesDisconnectedClient(Throwable ex) {
358+
return ("Broken pipe".equalsIgnoreCase(NestedExceptionUtils.getMostSpecificCause(ex).getMessage()) ||
359+
DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()));
360+
}
365361

366362

367363
// Delegation methods
@@ -421,7 +417,8 @@ public void tryCloseWithSockJsTransportError(Throwable error, CloseStatus closeS
421417
delegateError(error);
422418
}
423419
catch (Throwable delegateException) {
424-
// ignore
420+
// Ignore
421+
logger.debug("Exception from error handling delegate", delegateException);
425422
}
426423
try {
427424
close(closeStatus);

0 commit comments

Comments
 (0)