/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.util.exceptions; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Predicates.instanceOf; import static com.google.common.base.Throwables.getCausalChain; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.javalang.JavaClassNames; import org.apache.brooklyn.util.text.Strings; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; public class Exceptions { private static final List<Class<? extends Throwable>> BORING_THROWABLE_SUPERTYPES = ImmutableList.<Class<? extends Throwable>>of( ExecutionException.class, InvocationTargetException.class, PropagatedRuntimeException.class, UndeclaredThrowableException.class); private static boolean isBoring(Throwable t) { for (Class<? extends Throwable> type: BORING_THROWABLE_SUPERTYPES) if (type.isInstance(t)) return true; return false; } private static final Predicate<Throwable> IS_THROWABLE_BORING = new Predicate<Throwable>() { @Override public boolean apply(Throwable input) { return isBoring(input); } }; private static List<Class<? extends Throwable>> BORING_PREFIX_THROWABLE_EXACT_TYPES = ImmutableList.<Class<? extends Throwable>>of( IllegalStateException.class, RuntimeException.class, CompoundRuntimeException.class); /** Returns whether this is throwable either known to be boring or to have an unhelpful type name (prefix) * which should be suppressed. null is accepted but treated as not boring. */ public static boolean isPrefixBoring(Throwable t) { if (t==null) return false; if (isBoring(t)) return true; if (t instanceof UserFacingException) return true; for (Class<? extends Throwable> type: BORING_PREFIX_THROWABLE_EXACT_TYPES) if (t.getClass().equals(type)) return true; return false; } private static String stripBoringPrefixes(String s) { ArrayList<String> prefixes = Lists.newArrayListWithCapacity(2 + BORING_PREFIX_THROWABLE_EXACT_TYPES.size() * 3); for (Class<? extends Throwable> type : BORING_PREFIX_THROWABLE_EXACT_TYPES) { prefixes.add(type.getCanonicalName()); prefixes.add(type.getName()); prefixes.add(type.getSimpleName()); } prefixes.add(":"); prefixes.add(" "); String[] ps = prefixes.toArray(new String[prefixes.size()]); return Strings.removeAllFromStart(s, ps); } /** * Propagate a {@link Throwable} as a {@link RuntimeException}. * <p> * Like Guava {@link Throwables#propagate(Throwable)} but: * <li> throws {@link RuntimeInterruptedException} to handle {@link InterruptedException}s; and * <li> wraps as PropagatedRuntimeException for easier filtering */ public static RuntimeException propagate(Throwable throwable) { if (throwable instanceof InterruptedException) { throw new RuntimeInterruptedException((InterruptedException) throwable); } else if (throwable instanceof RuntimeInterruptedException) { Thread.currentThread().interrupt(); throw (RuntimeInterruptedException) throwable; } Throwables.propagateIfPossible(checkNotNull(throwable)); throw new PropagatedRuntimeException(throwable); } /** * See {@link #propagate(Throwable)}. If wrapping the exception, then include the given message; * otherwise the message is not used. */ public static RuntimeException propagate(String msg, Throwable throwable) { if (throwable instanceof InterruptedException) { throw new RuntimeInterruptedException(msg, (InterruptedException) throwable); } else if (throwable instanceof RuntimeInterruptedException) { Thread.currentThread().interrupt(); throw (RuntimeInterruptedException) throwable; } Throwables.propagateIfPossible(checkNotNull(throwable)); throw new PropagatedRuntimeException(msg, throwable); } /** * Propagate exceptions which are fatal. * <p> * Propagates only those exceptions which one rarely (if ever) wants to capture, * such as {@link InterruptedException} and {@link Error}s. */ public static void propagateIfFatal(Throwable throwable) { if (throwable instanceof InterruptedException) { throw new RuntimeInterruptedException((InterruptedException) throwable); } else if (throwable instanceof RuntimeInterruptedException) { Thread.currentThread().interrupt(); throw (RuntimeInterruptedException) throwable; } else if (throwable instanceof Error) { throw (Error) throwable; } } /** returns the first exception of the given type, or null */ @SuppressWarnings("unchecked") public static <T extends Throwable> T getFirstThrowableOfType(Throwable from, Class<T> clazz) { return (T) Iterables.tryFind(getCausalChain(from), instanceOf(clazz)).orNull(); } /** returns the first exception that matches the filter, or null */ public static Throwable getFirstThrowableMatching(Throwable from, Predicate<? super Throwable> filter) { return Iterables.tryFind(getCausalChain(from), filter).orNull(); } /** returns the first exception in the call chain which is not of common uninteresting types * (ie excluding ExecutionException and PropagatedRuntimeExceptions); * or the original throwable if all are uninteresting */ public static Throwable getFirstInteresting(Throwable throwable) { return Iterables.tryFind(getCausalChain(throwable), Predicates.not(IS_THROWABLE_BORING)).or(throwable); } /** creates (but does not throw) a new {@link PropagatedRuntimeException} whose * message and cause are taken from the first _interesting_ element in the source */ public static Throwable collapse(Throwable source) { return collapse(source, true); } /** as {@link #collapse(Throwable)} but includes causal messages in the message as per {@link #collapseTextIncludingAllCausalMessages(Throwable)}; * use with care (limit once) as repeated usage can result in multiple copies of the same message */ public static Throwable collapseIncludingAllCausalMessages(Throwable source) { return collapse(source, true, true, ImmutableSet.<Throwable>of()); } /** creates (but does not throw) a new {@link PropagatedRuntimeException} whose * message is taken from the first _interesting_ element in the source, * and optionally also the causal chain */ public static Throwable collapse(Throwable source, boolean collapseCausalChain) { return collapse(source, collapseCausalChain, false, ImmutableSet.<Throwable>of()); } private static Throwable collapse(Throwable source, boolean collapseCausalChain, boolean includeAllCausalMessages, Set<Throwable> visited) { visited = MutableSet.copyOf(visited); String message = ""; Throwable collapsed = source; int collapseCount = 0; boolean messageIsFinal = false; // remove boring stack traces at the head while (isBoring(collapsed) && !messageIsFinal) { collapseCount++; Throwable cause = collapsed.getCause(); if (cause==null) { // everything in the tree is boring... return source; } if (visited.add(collapsed)) { // there is a recursive loop break; } String collapsedS = collapsed.getMessage(); if (collapsed instanceof PropagatedRuntimeException && ((PropagatedRuntimeException)collapsed).isCauseEmbeddedInMessage()) { message = collapsed.getMessage(); messageIsFinal = true; } else if (Strings.isNonBlank(collapsedS)) { collapsedS = Strings.removeAllFromEnd(collapsedS, cause.toString(), stripBoringPrefixes(cause.toString()), cause.getMessage()); collapsedS = stripBoringPrefixes(collapsedS); if (Strings.isNonBlank(collapsedS)) message = appendSeparator(message, collapsedS); } collapsed = cause; } // if no messages so far (ie we will be the toString) then remove boring prefixes from the message Throwable messagesCause = collapsed; while (messagesCause!=null && isPrefixBoring(messagesCause) && Strings.isBlank(message)) { collapseCount++; if (Strings.isNonBlank(messagesCause.getMessage())) { message = messagesCause.getMessage(); messagesCause = messagesCause.getCause(); break; } visited.add(messagesCause); messagesCause = messagesCause.getCause(); } if (collapseCount==0 && !includeAllCausalMessages) return source; if (collapseCount==0 && messagesCause!=null) { message = messagesCause.toString(); messagesCause = messagesCause.getCause(); } if (messagesCause!=null && !messageIsFinal) { String extraMessage = collapseText(messagesCause, includeAllCausalMessages, ImmutableSet.copyOf(visited)); message = appendSeparator(message, extraMessage); } if (message==null) message = ""; return new PropagatedRuntimeException(message, collapseCausalChain ? collapsed : source, true); } static String appendSeparator(String message, String next) { if (Strings.isBlank(message)) return next; if (Strings.isBlank(next)) return message; if (message.endsWith(next)) return message; if (message.trim().endsWith(":") || message.trim().endsWith(";")) return message.trim()+" "+next; return message + ": " + next; } /** removes uninteresting items from the top of the call stack (but keeps interesting messages), and throws * @deprecated since 0.7.0 same as {@link #propagate(Throwable)} */ public static RuntimeException propagateCollapsed(Throwable source) { throw propagate(source); } /** like {@link #collapse(Throwable)} but returning a one-line message suitable for logging without traces */ public static String collapseText(Throwable t) { return collapseText(t, false); } /** normally {@link #collapseText(Throwable)} will stop following causal chains when encountering an interesting exception * with a message; this variant will continue to follow such causal chains, showing all messages. * for use e.g. when verbose is desired in the single-line message. */ public static String collapseTextIncludingAllCausalMessages(Throwable t) { return collapseText(t, true); } private static String collapseText(Throwable t, boolean includeAllCausalMessages) { return collapseText(t, includeAllCausalMessages, ImmutableSet.<Throwable>of()); } private static String collapseText(Throwable t, boolean includeAllCausalMessages, Set<Throwable> visited) { if (t == null) return null; if (visited.contains(t)) { // IllegalStateException sometimes refers to itself; guard against stack overflows if (Strings.isNonBlank(t.getMessage())) return t.getMessage(); else return "("+t.getClass().getName()+", recursive cause)"; } Throwable t2 = collapse(t, true, includeAllCausalMessages, visited); visited = MutableSet.copyOf(visited); visited.add(t); visited.add(t2); if (t2 instanceof PropagatedRuntimeException) { if (((PropagatedRuntimeException)t2).isCauseEmbeddedInMessage()) // normally return t2.getMessage(); else if (t2.getCause()!=null) return collapseText(t2.getCause(), includeAllCausalMessages, ImmutableSet.copyOf(visited)); return ""+t2.getClass(); } String result = t2.toString(); if (!includeAllCausalMessages) { return result; } Throwable cause = t2.getCause(); if (cause != null) { String causeResult = collapseText(new PropagatedRuntimeException(cause)); if (result.indexOf(causeResult)>=0) return result; return result + "; caused by "+causeResult; } return result; } public static RuntimeException propagate(Collection<? extends Throwable> exceptions) { throw propagate(create(exceptions)); } public static RuntimeException propagate(String prefix, Collection<? extends Throwable> exceptions) { throw propagate(create(prefix, exceptions)); } /** creates the given exception, but without propagating it, for use when caller will be wrapping */ public static Throwable create(Collection<? extends Throwable> exceptions) { return create(null, exceptions); } /** creates the given exception, but without propagating it, for use when caller will be wrapping */ public static RuntimeException create(@Nullable String prefix, Collection<? extends Throwable> exceptions) { if (exceptions.size()==1) { Throwable e = exceptions.iterator().next(); if (Strings.isBlank(prefix)) return new PropagatedRuntimeException(e); return new PropagatedRuntimeException(prefix + ": " + Exceptions.collapseText(e), e); } if (exceptions.isEmpty()) { if (Strings.isBlank(prefix)) return new CompoundRuntimeException("(empty compound exception)", exceptions); return new CompoundRuntimeException(prefix, exceptions); } if (Strings.isBlank(prefix)) return new CompoundRuntimeException(exceptions.size()+" errors, including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); return new CompoundRuntimeException(prefix+"; "+exceptions.size()+" errors including: " + Exceptions.collapseText(exceptions.iterator().next()), exceptions); } /** Some throwables require a prefix for the message to make sense, * for instance NoClassDefFoundError's message is often just the type. */ public static boolean isPrefixImportant(Throwable t) { if (t instanceof NoClassDefFoundError) return true; return false; } /** For {@link Throwable} instances where know {@link #isPrefixImportant(Throwable)}, * this returns a nice message for use as a prefix; otherwise this returns the class name. * Callers should typically suppress the prefix if {@link #isPrefixBoring(Throwable)} is true. */ public static String getPrefixText(Throwable t) { if (t instanceof NoClassDefFoundError) return "Type not found"; return JavaClassNames.cleanSimpleClassName(t); } }