View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.config;
8   
9   import org.mule.api.MuleException;
10  import org.mule.api.MuleRuntimeException;
11  import org.mule.api.config.ExceptionReader;
12  import org.mule.api.registry.ServiceType;
13  import org.mule.config.i18n.CoreMessages;
14  import org.mule.util.ClassUtils;
15  import org.mule.util.MapUtils;
16  import org.mule.util.SpiUtils;
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.lang.reflect.InvocationTargetException;
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Properties;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  /**
32   * <code>ExceptionHelper</code> provides a number of helper functions that can be
33   * useful for dealing with Mule exceptions. This class has 3 core functions - <p/> 1.
34   * ErrorCode lookup. A corresponding Mule error code can be found using for a given
35   * Mule exception 2. Additional Error information such as Java doc url for a given
36   * exception can be resolved using this class 3. Error code mappings can be looked up
37   * by providing the the protocol to map to and the Mule exception.
38   */
39  
40  public final class ExceptionHelper
41  {
42      /**
43       * This is the property to set the error code to no the message it is the
44       * property name the Transport provider uses set the set the error code on the
45       * underlying message
46       */
47      public static final String ERROR_CODE_PROPERTY = "error.code.property";
48  
49      /**
50       * logger used by this class
51       */
52      protected static final Log logger = LogFactory.getLog(ExceptionHelper.class);
53  
54      private static String J2SE_VERSION = "";
55  
56      /**
57       * todo How do you get the j2ee version??
58       */
59      private static final String J2EE_VERSION = "1.3ee";
60  
61      private static Properties errorDocs = new Properties();
62      private static Properties errorCodes = new Properties();
63      private static Map reverseErrorCodes = null;
64      private static Map errorMappings = new HashMap();
65  
66      private static int exceptionThreshold = 0;
67      private static boolean verbose = true;
68  
69      private static boolean initialised = false;
70  
71      /**
72       * A list of the exception readers to use for different types of exceptions
73       */
74      private static List<ExceptionReader> exceptionReaders = new ArrayList<ExceptionReader>();
75  
76      /**
77       * The default ExceptionReader which will be used for most types of exceptions
78       */
79      private static ExceptionReader defaultExceptionReader = new DefaultExceptionReader();
80  
81      static
82      {
83          initialise();
84      }
85  
86      /**
87       * Do not instanciate.
88       */
89      private ExceptionHelper()
90      {
91          // no-op
92      }
93  
94      private static void initialise()
95      {
96          try
97          {
98              if (initialised)
99              {
100                 return;
101             }
102 
103             registerExceptionReader(new MuleExceptionReader());
104             registerExceptionReader(new NamingExceptionReader());
105             J2SE_VERSION = System.getProperty("java.specification.version");
106 
107             String name = SpiUtils.SERVICE_ROOT + ServiceType.EXCEPTION.getPath()
108                     + "/mule-exception-codes.properties";
109             InputStream in = ExceptionHelper.class.getClassLoader().getResourceAsStream(name);
110             if (in == null)
111             {
112                 throw new IllegalArgumentException("Failed to load resource: " + name);
113             }
114             errorCodes.load(in);
115             in.close();
116 
117             reverseErrorCodes = MapUtils.invertMap(errorCodes);
118 
119             name = SpiUtils.SERVICE_ROOT + ServiceType.EXCEPTION.getPath()
120                     + "/mule-exception-config.properties";
121             in = ExceptionHelper.class.getClassLoader().getResourceAsStream(name);
122             if (in == null)
123             {
124                 throw new IllegalArgumentException("Failed to load resource: " + name);
125             }
126             errorDocs.load(in);
127             in.close();
128 
129             initialised = true;
130         }
131         catch (Exception e)
132         {
133             throw new MuleRuntimeException(CoreMessages.failedToLoad("Exception resources"), e);
134         }
135     }
136 
137     public static int getErrorCode(Class exception)
138     {
139         String code = errorCodes.getProperty(exception.getName(), "-1");
140         return Integer.parseInt(code);
141     }
142 
143     public static Class getErrorClass(int code)
144     {
145         String key = String.valueOf(code);
146         Object clazz = reverseErrorCodes.get(key);
147         if (clazz == null)
148         {
149             return null;
150         }
151         else if (clazz instanceof Class)
152         {
153             return (Class) clazz;
154         }
155         else
156         {
157             try
158             {
159                 clazz = ClassUtils.loadClass(clazz.toString(), ExceptionHelper.class);
160             }
161             catch (ClassNotFoundException e)
162             {
163                 logger.error(e.getMessage(), e);
164                 return null;
165             }
166             reverseErrorCodes.put(key, clazz);
167             return (Class) clazz;
168         }
169     }
170 
171     private static Properties getErrorMappings(String protocol)
172     {
173         Object m = errorMappings.get(protocol);
174         if (m != null)
175         {
176             if (m instanceof Properties)
177             {
178                 return (Properties) m;
179             }
180             else
181             {
182                 return null;
183             }
184         }
185         else
186         {
187             String name = SpiUtils.SERVICE_ROOT + ServiceType.EXCEPTION.getPath() + "/" + protocol + "-exception-mappings.properties";
188             InputStream in = ExceptionHelper.class.getClassLoader().getResourceAsStream(name);
189             if (in == null)
190             {
191                 errorMappings.put(protocol, "not found");
192                 if (logger.isDebugEnabled())
193                 {
194                     logger.debug("Failed to load error mappings from: " + name
195                             + " This may be because there are no error code mappings for protocol: "
196                             + protocol);
197                 }
198                 return null;
199             }
200 
201             Properties p = new Properties();
202             try
203             {
204                 p.load(in);
205                 in.close();
206             }
207             catch (IOException iox)
208             {
209                 throw new IllegalArgumentException("Failed to load resource: " + name);
210             }
211 
212             errorMappings.put(protocol, p);
213             return p;
214         }
215     }
216 
217     public static String getErrorCodePropertyName(String protocol)
218     {
219         protocol = protocol.toLowerCase();
220         Properties mappings = getErrorMappings(protocol);
221         if (mappings == null)
222         {
223             return null;
224         }
225         return mappings.getProperty(ERROR_CODE_PROPERTY);
226     }
227 
228     public static String getErrorMapping(String protocol, Class exception)
229     {
230         protocol = protocol.toLowerCase();
231         Properties mappings = getErrorMappings(protocol);
232         if (mappings == null)
233         {
234             logger.info("No mappings found for protocol: " + protocol);
235             return String.valueOf(getErrorCode(exception));
236         }
237 
238         Class clazz = exception;
239         String code = null;
240         while (!clazz.equals(Object.class))
241         {
242             code = mappings.getProperty(clazz.getName());
243             if (code == null)
244             {
245                 clazz = clazz.getSuperclass();
246             }
247             else
248             {
249                 return code;
250             }
251         }
252         code = String.valueOf(getErrorCode(exception));
253         // Finally lookup mapping based on error code and return the Mule error
254         // code if a match is not found
255         return mappings.getProperty(code, code);
256     }
257 
258     public static String getJavaDocUrl(Class<?> exception)
259     {
260         return getDocUrl("javadoc.", exception.getName());
261     }
262 
263     public static String getDocUrl(Class<?> exception)
264     {
265         return getDocUrl("doc.", exception.getName());
266     }
267 
268     private static String getDocUrl(String prefix, String packageName)
269     {
270         String key = prefix;
271         if (packageName.startsWith("java.") || packageName.startsWith("javax."))
272         {
273             key += J2SE_VERSION;
274         }
275         String url = getUrl(key, packageName);
276         if (url == null && (packageName.startsWith("java.") || packageName.startsWith("javax.")))
277         {
278             key = prefix + J2EE_VERSION;
279             url = getUrl(key, packageName);
280         }
281         if (url != null)
282         {
283             if (!url.endsWith("/"))
284             {
285                 url += "/";
286             }
287             String s = packageName.replaceAll("[.]", "/");
288             s += ".html";
289             url += s;
290         }
291         return url;
292     }
293 
294     private static String getUrl(String key, String packageName)
295     {
296         String url = null;
297         if (!key.endsWith("."))
298         {
299             key += ".";
300         }
301         while (packageName.length() > 0)
302         {
303             url = errorDocs.getProperty(key + packageName, null);
304             if (url == null)
305             {
306                 int i = packageName.lastIndexOf(".");
307                 if (i == -1)
308                 {
309                     packageName = "";
310                 }
311                 else
312                 {
313                     packageName = packageName.substring(0, i);
314                 }
315             }
316             else
317             {
318                 break;
319             }
320         }
321         return url;
322     }
323 
324     public static Throwable getRootException(Throwable t)
325     {
326         Throwable cause = t;
327         Throwable root = null;
328         while (cause != null)
329         {
330             root = cause;
331             cause = getExceptionReader(cause).getCause(cause);
332             // address some misbehaving exceptions, avoid endless loop
333             if (t == cause)
334             {
335                 break;
336             }
337         }
338 
339         return DefaultMuleConfiguration.fullStackTraces ? root : sanitize(root);
340     }
341 
342     public static Throwable sanitizeIfNeeded(Throwable t)
343     {
344         return DefaultMuleConfiguration.fullStackTraces ? t : sanitize(t);
345     }
346 
347     /**
348      * Removes some internal Mule entries from the stacktrace. Modifies the
349      * passed-in throwable stacktrace.
350      */
351     public static Throwable sanitize(Throwable t)
352     {
353         if (t == null)
354         {
355             return null;
356         }
357         StackTraceElement[] trace = t.getStackTrace();
358         List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>();
359         for (StackTraceElement stackTraceElement : trace)
360         {
361             if (!isMuleInternalClass(stackTraceElement.getClassName()))
362             {
363                 newTrace.add(stackTraceElement);
364             }
365         }
366 
367         StackTraceElement[] clean = new StackTraceElement[newTrace.size()];
368         newTrace.toArray(clean);
369         t.setStackTrace(clean);
370 
371         Throwable cause = t.getCause();
372         while (cause != null)
373         {
374             sanitize(cause);
375             cause = cause.getCause();
376         }
377 
378         return t;
379     }
380 
381 
382     /**
383      * Removes some internal Mule entries from the stacktrace. Modifies the
384      * passed-in throwable stacktrace.
385      */
386     public static Throwable summarise(Throwable t, int depth)
387     {
388         t = sanitize(t);
389         StackTraceElement[] trace = t.getStackTrace();
390 
391         int newStackDepth = Math.min(trace.length, depth);
392         StackTraceElement[] newTrace = new StackTraceElement[newStackDepth];
393 
394         System.arraycopy(trace, 0, newTrace, 0, newStackDepth);
395         t.setStackTrace(newTrace);
396         
397         return t;
398     }
399 
400     private static boolean isMuleInternalClass(String className)
401     {
402         /*
403            Sacrifice the code quality for the sake of keeping things simple -
404            the alternative would be to pass MuleContext into every exception constructor.
405         */
406         for (String mulePackage : DefaultMuleConfiguration.stackTraceFilter)
407         {
408             if (className.startsWith(mulePackage))
409             {
410                 return true;
411             }
412         }
413         return false;
414     }
415 
416     public static Throwable getRootParentException(Throwable t)
417     {
418         Throwable cause = t;
419         Throwable parent = t;
420         while (cause != null)
421         {
422             if (cause.getCause() == null)
423             {
424                 return parent;
425             }
426             parent = cause;
427             cause = getExceptionReader(cause).getCause(cause);
428             // address some misbehaving exceptions, avoid endless loop
429             if (t == cause)
430             {
431                 break;
432             }
433         }
434         return t;
435     }
436 
437     public static MuleException getRootMuleException(Throwable t)
438     {
439         Throwable cause = t;
440         MuleException exception = null;
441         while (cause != null)
442         {
443             if (cause instanceof MuleException)
444             {
445                 exception = (MuleException) cause;
446             }
447             final Throwable tempCause = getExceptionReader(cause).getCause(cause);
448             if (DefaultMuleConfiguration.fullStackTraces)
449             {
450                 cause = tempCause;
451             }
452             else
453             {
454                 cause = ExceptionHelper.sanitize(tempCause);
455             }
456             // address some misbehaving exceptions, avoid endless loop
457             if (t == cause)
458             {
459                 break;
460             }
461         }
462         return exception;
463     }
464 
465     public static List getExceptionsAsList(Throwable t)
466     {
467         List exceptions = new ArrayList();
468         Throwable cause = t;
469         while (cause != null)
470         {
471             exceptions.add(0, cause);
472             cause = getExceptionReader(cause).getCause(cause);
473             // address some misbehaving exceptions, avoid endless loop
474             if (t == cause)
475             {
476                 break;
477             }
478         }
479         return exceptions;
480     }
481 
482     public static Map getExceptionInfo(Throwable t)
483     {
484         Map info = new HashMap();
485         Throwable cause = t;
486         while (cause != null)
487         {
488             info.putAll(getExceptionReader(cause).getInfo(cause));
489             cause = getExceptionReader(cause).getCause(cause);
490             // address some misbehaving exceptions, avoid endless loop
491             if (t == cause)
492             {
493                 break;
494             }
495         }
496         return info;
497     }
498 
499     public static String getExceptionStack(Throwable t)
500     {
501         StringBuffer buf = new StringBuffer();
502         // get exception stack
503         List exceptions = getExceptionsAsList(t);
504 
505         int i = 1;
506         for (Iterator iterator = exceptions.iterator(); iterator.hasNext(); i++)
507         {
508             if (i > exceptionThreshold && exceptionThreshold > 0)
509             {
510                 buf.append("(").append(exceptions.size() - i + 1).append(" more...)");
511                 break;
512             }
513             Throwable throwable = (Throwable) iterator.next();
514             ExceptionReader er = getExceptionReader(throwable);
515             buf.append(i).append(". ").append(er.getMessage(throwable)).append(" (");
516             buf.append(throwable.getClass().getName()).append(")\n");
517             if (verbose && throwable.getStackTrace().length > 0)
518             {
519                 StackTraceElement e = throwable.getStackTrace()[0];
520                 buf.append("  ")
521                         .append(e.getClassName())
522                         .append(":")
523                         .append(e.getLineNumber())
524                         .append(" (")
525                         .append(getJavaDocUrl(throwable.getClass()))
526                         .append(")\n");
527             }
528         }
529         return buf.toString();
530     }
531 
532     /**
533      * Registers an exception reader with Mule
534      *
535      * @param reader the reader to register.
536      */
537     public static void registerExceptionReader(ExceptionReader reader)
538     {
539         exceptionReaders.add(reader);
540     }
541 
542     /**
543      * Gets an exception reader for the exception
544      *
545      * @param t the exception to get a reader for
546      * @return either a specific reader or an instance of DefaultExceptionReader.
547      *         This method never returns null;
548      */
549     public static ExceptionReader getExceptionReader(Throwable t)
550     {
551         for (ExceptionReader exceptionReader : exceptionReaders)
552         {
553             if (exceptionReader.getExceptionType().isInstance(t))
554             {
555                 return exceptionReader;
556             }
557         }
558         return defaultExceptionReader;
559     }
560 
561     public static String writeException(Throwable t)
562     {
563         ExceptionReader er = getExceptionReader(t);
564         StringBuffer msg = new StringBuffer();
565         msg.append(er.getMessage(t)).append(". Type: ").append(t.getClass());
566         return msg.toString();
567     }
568 
569     public static <T extends Throwable>T unwrap(T t)
570     {
571         if(t instanceof InvocationTargetException)
572         {
573             return (T)((InvocationTargetException)t).getTargetException();
574         }
575         return t;
576 
577     }
578 }