Common root cause introspection algorithm in NestedExceptionUtils
Issue: SPR-15510
(cherry picked from commit 9d8e9cf)
This commit is contained in:
parent
aa8cf190d3
commit
2d1b551248
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @return the innermost exception, or {@code null} if none
|
||||||
*/
|
*/
|
||||||
public Throwable getRootCause() {
|
public Throwable getRootCause() {
|
||||||
Throwable rootCause = null;
|
return NestedExceptionUtils.getRootCause(this);
|
||||||
Throwable cause = getCause();
|
|
||||||
while (cause != null && cause != rootCause) {
|
|
||||||
rootCause = cause;
|
|
||||||
cause = cause.getCause();
|
|
||||||
}
|
|
||||||
return rootCause;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @return the full exception message
|
||||||
*/
|
*/
|
||||||
public static String buildMessage(String message, Throwable cause) {
|
public static String buildMessage(String message, Throwable cause) {
|
||||||
if (cause != null) {
|
if (cause == null) {
|
||||||
StringBuilder sb = new StringBuilder();
|
return message;
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder(64);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
sb.append(message).append("; ");
|
sb.append(message).append("; ");
|
||||||
}
|
}
|
||||||
sb.append("nested exception is ").append(cause);
|
sb.append("nested exception is ").append(cause);
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return message;
|
/**
|
||||||
|
* 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.
|
||||||
|
* <p>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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public Throwable getRootCause() {
|
public Throwable getRootCause() {
|
||||||
Throwable rootCause = null;
|
return NestedExceptionUtils.getRootCause(this);
|
||||||
Throwable cause = getCause();
|
|
||||||
while (cause != null && cause != rootCause) {
|
|
||||||
rootCause = cause;
|
|
||||||
cause = cause.getCause();
|
|
||||||
}
|
|
||||||
return rootCause;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package org.springframework.web.socket.sockjs.transport.session;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -31,7 +30,7 @@ import java.util.concurrent.ScheduledFuture;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.core.NestedCheckedException;
|
import org.springframework.core.NestedExceptionUtils;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.socket.CloseStatus;
|
import org.springframework.web.socket.CloseStatus;
|
||||||
import org.springframework.web.socket.TextMessage;
|
import org.springframework.web.socket.TextMessage;
|
||||||
|
|
@ -70,25 +69,22 @@ public abstract class AbstractSockJsSession implements SockJsSession {
|
||||||
public static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
|
public static final String DISCONNECTED_CLIENT_LOG_CATEGORY =
|
||||||
"org.springframework.web.socket.sockjs.DisconnectedClient";
|
"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<String> DISCONNECTED_CLIENT_EXCEPTIONS =
|
||||||
|
new HashSet<String>(Arrays.asList("ClientAbortException", "EOFException", "EofException"));
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Separate logger to use on network IO failure after a client has gone away.
|
* Separate logger to use on network IO failure after a client has gone away.
|
||||||
* @see #DISCONNECTED_CLIENT_LOG_CATEGORY
|
* @see #DISCONNECTED_CLIENT_LOG_CATEGORY
|
||||||
*/
|
*/
|
||||||
protected static final Log disconnectedClientLogger = LogFactory.getLog(DISCONNECTED_CLIENT_LOG_CATEGORY);
|
protected static final Log disconnectedClientLogger = LogFactory.getLog(DISCONNECTED_CLIENT_LOG_CATEGORY);
|
||||||
|
|
||||||
|
|
||||||
private static final Set<String> disconnectedClientExceptions;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Set<String> set = new HashSet<String>(4);
|
|
||||||
set.add("ClientAbortException"); // Tomcat
|
|
||||||
set.add("EOFException"); // Tomcat
|
|
||||||
set.add("EofException"); // Jetty
|
|
||||||
// java.io.IOException "Broken pipe" on WildFly, Glassfish (already covered)
|
|
||||||
disconnectedClientExceptions = Collections.unmodifiableSet(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
protected final Object responseLock = new Object();
|
protected final Object responseLock = new Object();
|
||||||
|
|
@ -340,28 +336,28 @@ public abstract class AbstractSockJsSession implements SockJsSession {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logWriteFrameFailure(Throwable failure) {
|
protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException;
|
||||||
@SuppressWarnings("serial")
|
|
||||||
NestedCheckedException nestedException = new NestedCheckedException("", failure) {};
|
|
||||||
|
|
||||||
if ("Broken pipe".equalsIgnoreCase(nestedException.getMostSpecificCause().getMessage()) ||
|
|
||||||
disconnectedClientExceptions.contains(failure.getClass().getSimpleName())) {
|
|
||||||
|
|
||||||
|
private void logWriteFrameFailure(Throwable ex) {
|
||||||
|
if (indicatesDisconnectedClient(ex)) {
|
||||||
if (disconnectedClientLogger.isTraceEnabled()) {
|
if (disconnectedClientLogger.isTraceEnabled()) {
|
||||||
disconnectedClientLogger.trace("Looks like the client has gone away", failure);
|
disconnectedClientLogger.trace("Looks like the client has gone away", ex);
|
||||||
}
|
}
|
||||||
else if (disconnectedClientLogger.isDebugEnabled()) {
|
else if (disconnectedClientLogger.isDebugEnabled()) {
|
||||||
disconnectedClientLogger.debug("Looks like the client has gone away: " +
|
disconnectedClientLogger.debug("Looks like the client has gone away: " + ex +
|
||||||
nestedException.getMessage() + " (For full stack trace, set the '" +
|
" (For a full stack trace, set the log category '" + DISCONNECTED_CLIENT_LOG_CATEGORY +
|
||||||
DISCONNECTED_CLIENT_LOG_CATEGORY + "' log category to TRACE level)");
|
"' to TRACE level.)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logger.debug("Terminating connection after failure to send message to client", failure);
|
logger.debug("Terminating connection after failure to send message to client", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void writeFrameInternal(SockJsFrame frame) throws IOException;
|
private boolean indicatesDisconnectedClient(Throwable ex) {
|
||||||
|
return ("Broken pipe".equalsIgnoreCase(NestedExceptionUtils.getMostSpecificCause(ex).getMessage()) ||
|
||||||
|
DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Delegation methods
|
// Delegation methods
|
||||||
|
|
@ -421,7 +417,8 @@ public abstract class AbstractSockJsSession implements SockJsSession {
|
||||||
delegateError(error);
|
delegateError(error);
|
||||||
}
|
}
|
||||||
catch (Throwable delegateException) {
|
catch (Throwable delegateException) {
|
||||||
// ignore
|
// Ignore
|
||||||
|
logger.debug("Exception from error handling delegate", delegateException);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
close(closeStatus);
|
close(closeStatus);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue