View Javadoc

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