View Javadoc

1   /*
2   Copyright (C) 2000 - 2007 Grid Systems, S.A.
3   
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2, as
6   published by the Free Software Foundation.
7   
8   This program is distributed in the hope that it will be useful,
9   but WITHOUT ANY WARRANTY; without even the implied warranty of
10  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  GNU General Public License for more details.
12  
13  You should have received a copy of the GNU General Public License
14  along with this program; if not, write to the Free Software
15  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  */
17  package com.gridsystems.innergrid.kernel;
18  
19  import java.io.PrintStream;
20  import java.io.PrintWriter;
21  import java.rmi.RemoteException;
22  import java.text.MessageFormat;
23  import java.util.ArrayList;
24  import java.util.Vector;
25  import java.util.regex.Pattern;
26  
27  import javax.xml.namespace.QName;
28  
29  import org.apache.axis.AxisFault;
30  import org.apache.axis.Constants;
31  import org.apache.axis.encoding.SerializationContext;
32  import org.apache.axis.utils.JavaUtils;
33  import org.apache.axis.utils.XMLUtils;
34  import org.w3c.dom.DOMException;
35  import org.w3c.dom.Element;
36  import org.w3c.dom.Node;
37  
38  /**
39   * Exception type for all published APIs.
40   * <p>
41   * All exceptions should be converted into a KernelException before being thrown
42   * in a public API method. Internal methods can also throw a KernelException if
43   * an error is considered to be of interest for a public API calling them.
44   * <p>
45   * Subclassing is highly encouraged for improving code readability and maintainability.
46   * For those cases where another base exception class is mandatory, see the
47   * {@link com.gridsystems.innergrid.kernel.Kernelizable Kernelizable} interface.
48   * <p>
49   * <b>IMPORTANT</b>: The parameter list is transferred to the client side as a string
50   * list. Methods for parsing those strings will be convenient if there is a need of
51   * obtaining the original values in the client.
52   *
53   * @author Rodrigo Ruiz
54   * @see com.gridsystems.innergrid.kernel.Kernelizable
55   */
56  public class KernelException extends AxisFault implements Kernelizable {
57  
58    /**
59     * Serial Version UID.
60     */
61    private static final long serialVersionUID = 1876438763487634L;
62  
63    /**
64     * The message for this exception.
65     */
66    protected String msg;
67  
68    /**
69     * The pattern used to generate the message.
70     */
71    protected String pattern;
72  
73    /**
74     * The list of error message parameters.
75     */
76    protected Object[] params;
77  
78    /**
79     * Creates an instance with a fixed message (no I18N).
80     *
81     * @param code    The error code
82     * @param pattern The message pattern (without parameter replacement)
83     * @param params  The error message parameters
84     * @param cause   The root cause
85     * @deprecated
86     */
87    public KernelException(String code, String pattern, Object[] params, Throwable cause) {
88      this(cause, new QName("Kernel", code), pattern, params);
89    }
90  
91    /**
92     * Creates an instance with a fixed message (no I18N).
93     *
94     * @param code    The fully qualified error code
95     * @param pattern The message pattern (without parameter replacement)
96     * @param params  The error message parameters
97     * @param cause   The root cause
98     * @deprecated
99     */
100   public KernelException(QName code, String pattern, Object[] params, Throwable cause) {
101     super(null, cause);
102     super.setFaultCode(code);
103     this.pattern = pattern;
104     this.params = params;
105     if (pattern == null) {
106       msg = ""; // The fault string should never be null
107     } else {
108       if (params == null) {
109         msg = pattern;
110       } else {
111         msg = MessageFormat.format(pattern, params);
112       }
113     }
114     super.setFaultString(msg);
115     super.setFaultReason(msg);
116   }
117 
118   /**
119    * Creates an instance with a fixed message (no I18N).
120    *
121    * @param cause   Root cause
122    * @param code    Error code
123    * @param pattern Message pattern (without parameter replacement)
124    * @param params  Message parameters
125    */
126   public KernelException(Throwable cause, String code, String pattern, Object... params) {
127     this(cause, new QName("Kernel", code), pattern, params);
128   }
129 
130   /**
131    * Creates an instance with a fixed message (no I18N).
132    *
133    * @param cause   Root cause
134    * @param code    Fully qualified error code
135    * @param pattern Message pattern (without parameter replacement)
136    * @param params  Message parameters
137    */
138   public KernelException(Throwable cause, QName code, String pattern, Object... params) {
139     super(null, cause);
140     super.setFaultCode(code);
141     this.pattern = pattern;
142     this.params = params;
143     if (pattern == null) {
144       msg = ""; // The fault string should never be null
145     } else {
146       if (params == null) {
147         msg = pattern;
148       } else {
149         msg = MessageFormat.format(pattern, params);
150       }
151     }
152     super.setFaultString(msg);
153     super.setFaultReason(msg);
154   }
155 
156   /**
157    * Gets this instance error code.
158    *
159    * @return The error code of this exception
160    */
161   public String getCode() {
162     QName code = super.getFaultCode();
163     String local = code.getLocalPart();
164     int pos = local.indexOf('}');
165 
166     return local.substring(pos + 1);
167   }
168 
169   /**
170    * Compares this instance error code with the specified one and returns true if they
171    * are equals.
172    *
173    * @param code The code to compare to
174    * @return true if the specified code is equals to this instance one
175    */
176   public boolean hasCode(String code) {
177     QName qcode = super.getFaultCode();
178     return qcode.getLocalPart().equals(code);
179   }
180 
181   /**
182    * Gets this fault code namespace.
183    *
184    * @return The faul code namespace
185    */
186   public String getNamespace() {
187     QName code = super.getFaultCode();
188     return code.getNamespaceURI();
189   }
190 
191   /**
192    * Gets the list of parameters for this exception message.
193    *
194    * @return The message parameters list
195    */
196   public Object[] getParams() {
197     return this.params;
198   }
199 
200   /**
201    * Gets this exception message.
202    * <p>
203    * The message is obtained from the pattern and the list of parameters.
204    *
205    * @return the message for this exception
206    */
207   @Override public String getMessage() {
208     return this.msg;
209   }
210 
211   /**
212    * Gets this exception pattern.
213    *
214    * @return This exception pattern.
215    */
216   public String getPattern() {
217     return this.pattern;
218   }
219 
220   /**
221    * Overrides the default implementation, returning the concatenation of
222    * this exception code and message.
223    *
224    * @return a human-readable description of this instance
225    */
226   @Override public String toString() {
227     return getCode() + ": " + getMessage();
228   }
229 
230   /**
231    * Parses a RemoteException instance, and if it contains a KernelException, returns
232    * a properly initialized one. Otherwise, it wraps the Exception into a
233    * KernelException with UNK000 code
234    *
235    * @param e The source remote exception
236    * @return  A properly initialized KernelException instance
237    */
238   public static KernelException fromRemoteException(RemoteException e) {
239     if (e instanceof KernelException) {
240       return (KernelException)e;
241     }
242 
243     if (e instanceof AxisFault) {
244       AxisFault fault = (AxisFault)e;
245       Element[] details = fault.getFaultDetails();
246 
247       QName code = null;
248       String pattern = fault.getFaultString();
249       Throwable cause = fault.getCause();
250       Object[] params = null;
251       ArrayList<String> list = new ArrayList<String>();
252 
253       // Parses the details array
254       int count = details == null ? 0 : details.length;
255       for (int i = 0; i < count; i++) {
256         String nodeName  = details[i].getLocalName();
257         String nodeText  = getTextContent(details[i]);
258 
259         if ("KernelException".equals(nodeName) && "true".equals(nodeText)) {
260           // Is a KernelException, parses it
261           code = fault.getFaultCode();
262         }
263         if ("parameter".equals(nodeName)) {
264           list.add(nodeText);
265         }
266         if ("pattern".equals(nodeName)) {
267           pattern = nodeText;
268         }
269       }
270 
271       // If it is not a KernelException, uses a generic UNK000 code
272       if (code == null) {
273         log.debug("The exception " + e.getMessage()
274           + " has been wrapped in a UNK000 exception", e);
275         return new KernelException("UNK000", pattern, null, fault);
276       }
277 
278       // Put the parsed parameters into an array
279       params = new String[list.size()];
280       list.toArray(params);
281 
282       return new KernelException(code, pattern, params, cause);
283     }
284 
285     // It is an unknown RemoteException subclass, simply wraps it
286     return new KernelException("UNK000", e.getMessage(), null, e);
287   }
288 
289   /**
290    * {@inheritDoc}
291    */
292   public KernelException toKernelException() {
293     return this;
294   }
295 
296   /**
297    * Gets the text content of the specified node.
298    *
299    * @param node  The node to retrieve the text from
300    * @return      The node text
301    * @throws DOMException If an error occurs
302    */
303   private static String getTextContent(Node node) throws DOMException {
304     StringBuffer sb = new StringBuffer();
305     getTextContent(node, sb);
306     return sb.toString();
307   }
308 
309   /**
310    * Gets the text content of the specified node.
311    * <p>
312    * If the node is of Text type, its value is returned. Otherwise, text content from
313    * all its children nodes is concatenated into a single string.
314    *
315    * @param node  The node whose contents we must retrieve.
316    * @param buf   The string buffer to write the result to
317    * @throws DOMException If an error occurs
318    */
319   private static void getTextContent(Node node, StringBuffer buf) throws DOMException {
320     if (node.getNodeType() == Node.TEXT_NODE) {
321       buf.append(node.getNodeValue());
322     }
323 
324     Node child = node.getFirstChild();
325     while (child != null) {
326       if (child.getNodeType() == Node.TEXT_NODE) {
327         buf.append(child.getNodeValue());
328       }
329       child = child.getNextSibling();
330     }
331   }
332 
333   /**
334    * Fixes the internal AxisFault detail list with the extensions of
335    * KernelException.
336    *
337    * @param context The serialization context passed from the Axis servlet
338    * @throws Exception if an error occurs while serializing the exception
339    */
340   @Override
341   public void output(SerializationContext context) throws Exception {
342     prepare();
343     super.output(context);
344   }
345 
346   /**
347    * Inserts all non-standard field values as XML elements into the details
348    * section of the SOAP exception.
349    */
350   @SuppressWarnings("unchecked")
351   protected void prepare() {
352     faultDetails = new Vector();
353 
354     setFaultString(getMessage());
355 
356     Element el;
357 
358     // Mark this exception as a KernelException instance
359     el = XMLUtils.StringToElement(Constants.NS_URI_AXIS, "KernelException", "true");
360     faultDetails.add(el);
361 
362     // Adds the list of parameters as standard Details elements
363     int count = (params == null) ? 0 : params.length;
364     for (int i = 0; i < count; i++) {
365       el = XMLUtils.StringToElement(Constants.NS_URI_AXIS, "parameter",
366                                     params[i] == null ? "null" : params[i].toString());
367       faultDetails.add(el);
368     }
369 
370     // Adds the pattern as a standard detail element
371     el =  XMLUtils.StringToElement(Constants.NS_URI_AXIS, "pattern", getPattern());
372     faultDetails.add(el);
373 
374     Throwable cause = getCause();
375     if (cause != null) {
376       // Put the exception class into the "exceptionName" element in the details.
377       // This allows us to get back a correct Java Exception class on the other side
378       // (assuming they have it available).
379       el = XMLUtils.StringToElement(Constants.NS_URI_AXIS, "exceptionName",
380                                             cause.getClass().getName());
381 
382       faultDetails.add(el);
383 
384       el =  XMLUtils.StringToElement(Constants.NS_URI_AXIS, "stackTrace",
385                                      JavaUtils.stackToString(cause));
386 
387       faultDetails.add(el);
388     }
389   }
390 
391   /**
392    * Stripped stacktrace.
393    *
394    * {@inheritDoc}
395    */
396   @Override public void printStackTrace(PrintWriter out) {
397     synchronized (out) {
398       out.println(this.getCode() + ": " + this.getMessage());
399 
400       Throwable cause = this.getCause();
401       if (cause != null) {
402         out.println("Caused By:");
403         out.println(cause.toString());
404       } else {
405         cause = this;
406       }
407       StackTraceElement[] trace = cause.getStackTrace();
408       int count = trace == null ? 0 : trace.length;
409       int max = traceLength(trace);
410       for (int i = 0; i < max; i++) {
411         out.println("\tat " + trace[i]);
412       }
413       if (max < count) {
414         out.println("\tat " + trace[max]);
415         out.println("\t... [" + (count - max - 1) + " elements omitted]");
416       }
417     }
418   }
419 
420   /**
421    * Stripped stacktrace.
422    *
423    * {@inheritDoc}
424    */
425   @Override public void printStackTrace(PrintStream out) {
426     synchronized (out) {
427       out.println(this.getCode() + ": " + this.getMessage());
428 
429       Throwable cause = this.getCause();
430       if (cause != null) {
431         out.println("Caused By:");
432         out.println(cause.toString());
433       } else {
434         cause = this;
435       }
436       StackTraceElement[] trace = cause.getStackTrace();
437       int count = trace == null ? 0 : trace.length;
438       int max = traceLength(trace);
439       for (int i = 0; i < max; i++) {
440         out.println("\tat " + trace[i]);
441       }
442       if (max < count) {
443         out.println("\tat " + trace[max]);
444         out.println("\t... [" + (count - max - 1) + " elements omitted]");
445       }
446     }
447   }
448 
449   /**
450    * Gets the number of elements of the specified stack trace to print.
451    *
452    * @param trace The elements to check
453    * @return The number of elements to print
454    */
455   private static int traceLength(StackTraceElement[] trace) {
456     int count = trace == null ? 0 : trace.length;
457     boolean skip = false;
458 
459     for (int i = 0; i < count; i++) {
460       String s = trace[i].getClassName() + '.' + trace[i].getMethodName();
461 
462       boolean match = excludePattern.matcher(s).matches();
463       if (skip && match) {
464         return i;
465       } else if (!skip) {
466         skip = !match;
467       }
468     }
469 
470     return count;
471   }
472 
473   /**
474    * Class exclussion pattern for stack traces.
475    */
476   private static Pattern excludePattern
477     = Pattern.compile("(^(java\\.|javax\\.|sun\\.reflect\\.).+)"
478                       + "|^(.+(\\.ApiProvider\\.invokeMethod))");
479 
480 }