001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata masks may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: ThrowableHandling.java,v $
031: * Revision 1.10 2005/10/12 18:36:33 colinmacleod
032: * Standardized format of Logger declaration - to make it easier to find instances
033: * which are not both static and final.
034: *
035: * Revision 1.9 2005/10/03 21:32:22 colinmacleod
036: * Cosmetic (line length) fix.
037: *
038: * Revision 1.8 2005/10/03 21:31:29 colinmacleod
039: * Fixed the comparison order or cause properties, so that rootCause is checked
040: * before cause.
041: *
042: * Revision 1.7 2005/10/03 10:17:25 colinmacleod
043: * Fixed some style and javadoc issues.
044: *
045: * Revision 1.6 2005/10/02 14:06:32 colinmacleod
046: * Added/improved log4j logging.
047: *
048: * Revision 1.5 2005/04/09 18:04:17 colinmacleod
049: * Changed copyright text to GPL v2 explicitly.
050: *
051: * Revision 1.4 2005/01/19 12:43:00 colinmacleod
052: * Fixed getCause: it was only returning the immediate cause.
053: *
054: * Revision 1.3 2005/01/11 10:51:02 colinmacleod
055: * Added log tracing.
056: *
057: * Revision 1.2 2005/01/06 22:21:45 colinmacleod
058: * Moved up a version number.
059: * Changed copyright notices to 2005.
060: * Updated the documentation:
061: * - started working on multiproject:site docu.
062: * - changed the logo.
063: * Added checkstyle and fixed LOADS of style issues.
064: * Added separate thirdparty subproject.
065: * Added struts (in web), util and webgui (in webtheme) from ivata op.
066: *
067: * Revision 1.1 2004/12/31 19:17:09 colinmacleod
068: * Renamed from ExceptionHandling to ThrowableHandling.
069: *
070: * Revision 1.1 2004/09/30 15:16:04 colinmacleod
071: * Split off addressbook elements into security subproject.
072: *
073: * Revision 1.2 2004/03/21 21:16:19 colinmacleod
074: * Shortened name to ivata op.
075: *
076: * Revision 1.1.1.1 2004/01/27 20:58:00 colinmacleod
077: * Moved ivata op to SourceForge.
078: *
079: * Revision 1.2 2003/10/17 12:36:13 jano
080: * fixing problems with building
081: * converting intranet -> portal
082: * Eclipse building
083: *
084: * Revision 1.1.1.1 2003/10/13 20:50:15 colin
085: * Restructured portal into subprojects
086: *
087: * Revision 1.7 2003/02/24 19:27:31 colin
088: * restructured file paths
089: *
090: * Revision 1.6 2003/02/04 17:43:52 colin
091: * copyright notice
092: *
093: * Revision 1.5 2002/11/12 10:16:10 colin
094: * added getRemoteException method with support for EJBException
095: *
096: * Revision 1.4 2002/09/16 14:18:15 colin
097: * improved the error messages for embedded exceptions
098: *
099: * Revision 1.3 2002/09/09 13:39:55 colin
100: * fixed bug in the displaying of the exception class
101: *
102: * Revision 1.2 2002/08/29 16:40:23 colin
103: * improved documentation
104: *
105: * Revision 1.1 2002/06/13 11:22:21 colin
106: * first version with rose model integration.
107: * -----------------------------------------------------------------------------
108: */
109: package com.ivata.mask.util;
110:
111: import java.beans.PropertyDescriptor;
112: import java.lang.reflect.InvocationTargetException;
113: import org.apache.commons.beanutils.PropertyUtils;
114: import org.apache.log4j.Logger;
115:
116: /**
117: * <p>
118: * Routines for handling exceptions.
119: * </p>
120: * <p>
121: * Don't create an instance of this class; use the static final methods.
122: * </p>
123: *
124: * @since ivata masks 0.4 (2002-05-20)
125: * @author Colin MacLeod
126: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
127: * @version $Revision: 1.10 $
128: */
129: public final class ThrowableHandling {
130: /**
131: * <p>
132: * This array of string contains all the known methods which will return a
133: * cause exception/throwable. The order is the order they will be searched
134: * for.
135: * </p>
136: */
137: private static final String[] CAUSE_PROPERTIES = { "rootCause",
138: "targetException", "undeclaredThrowable", "causedBy",
139: "cause" };
140: /**
141: * <p>
142: * Used to identify the preprended text in embedded exceptions.
143: * </p>
144: */
145: static final String[] EMBEDDED = { "Embedded Exception",
146: "SystemException is:" };
147: /**
148: * Logger for this class.
149: */
150: private static final Logger logger = Logger
151: .getLogger(ThrowableHandling.class);
152:
153: /**
154: * <p>
155: * If the parameter provided is a <code>RemoteException</code>, then
156: * provide the original exception, otherwise just return this exception.
157: * </p>
158: *
159: * @param throwable
160: * throwable object to return the original exception for.
161: * @return the exception which originally was thrown.
162: */
163: public static Throwable getCause(final Throwable throwable) {
164: if (logger.isDebugEnabled()) {
165: logger.debug("getCause(Throwable throwable = " + throwable
166: + ") - start");
167: }
168:
169: if (throwable == null) {
170: if (logger.isDebugEnabled()) {
171: logger
172: .debug("getCause(Throwable) - end - return value = "
173: + null);
174: }
175: return null;
176: }
177: Throwable returnThrowable = throwable;
178: Throwable cause;
179: while (((cause = getCauseOrNull(returnThrowable)) != null)
180: && (cause != returnThrowable)) {
181: returnThrowable = cause;
182: }
183:
184: if (logger.isDebugEnabled()) {
185: logger.debug("getCause(Throwable) - end - return value = "
186: + returnThrowable);
187: }
188: return returnThrowable;
189: }
190:
191: /**
192: * <p>
193: * If the given throwable was caused by another, get that cause.
194: * </p>
195: *
196: * @param throwable
197: * the throwable to find the cause for
198: * @return cause of this throwable, or <code>null</code> if there is no
199: * original cause.
200: */
201: private static Throwable getCauseOrNull(final Throwable throwable) {
202: if (logger.isDebugEnabled()) {
203: logger.debug("getCauseOrNull(Throwable throwable = "
204: + throwable + ") - start");
205: }
206:
207: if (throwable == null) {
208: if (logger.isDebugEnabled()) {
209: logger.debug("getCauseOrNull: throwable was null");
210: }
211: return null;
212: }
213: PropertyDescriptor[] descriptors = PropertyUtils
214: .getPropertyDescriptors(throwable.getClass());
215: // find the descriptor which matches a known name
216: String causeProperty = null;
217: outerLoop: for (int j = 0; j < ThrowableHandling.CAUSE_PROPERTIES.length; j++) {
218: String property = ThrowableHandling.CAUSE_PROPERTIES[j];
219: for (int i = 0; i < descriptors.length; i++) {
220: PropertyDescriptor descriptor = descriptors[i];
221: if (logger.isDebugEnabled()) {
222: logger.debug("Comparing property '"
223: + descriptor.getName()
224: + "' to known cause property '" + property
225: + "'");
226: }
227: if (property.equals(descriptor.getName())) {
228: if (logger.isDebugEnabled()) {
229: logger.debug("getCauseOrNull: using '"
230: + property + "' as cause property");
231: }
232: causeProperty = property;
233: break outerLoop;
234: }
235: }
236: }
237: // if there is no property to find the cause, there is not much we can
238: // do...
239: if (causeProperty == null) {
240: if (logger.isDebugEnabled()) {
241: logger.debug("getCauseOrNull: cause property is null");
242: }
243: return null;
244: }
245: try {
246: Throwable returnThrowable = (Throwable) PropertyUtils
247: .getProperty(throwable, causeProperty);
248: if (logger.isDebugEnabled()) {
249: logger
250: .debug("getCauseOrNull(Throwable) - end - return value = "
251: + returnThrowable);
252: }
253: return returnThrowable;
254: } catch (IllegalAccessException e) {
255: logger.error("getCauseOrNull(Throwable)", e);
256:
257: if (logger.isDebugEnabled()) {
258: logger.debug(e);
259: }
260: return null;
261: } catch (InvocationTargetException e) {
262: logger.error("getCauseOrNull(Throwable)", e);
263:
264: if (logger.isDebugEnabled()) {
265: logger.debug(e);
266: }
267: return null;
268: } catch (NoSuchMethodException e) {
269: logger.error("getCauseOrNull(Throwable)", e);
270:
271: if (logger.isDebugEnabled()) {
272: logger.debug(e);
273: }
274: return null;
275: }
276: }
277:
278: /**
279: * <p>
280: * If the parameter provided is a <code>RemoteException</code>, then
281: * provide the message from the original exception, otherwise just return
282: * the message of this exception.
283: * </p>
284: *
285: * @param throwable
286: * throwable object to return the original exception message for.
287: * @return the message of the exception which originally was thrown.
288: */
289: public static String getRemoteMessage(final Throwable throwable) {
290: if (logger.isDebugEnabled()) {
291: logger.debug("getRemoteMessage(Throwable throwable = "
292: + throwable + ") - start");
293: }
294:
295: Throwable remoteException = getCause(throwable);
296: String messageText = getCause(remoteException).getMessage();
297: int index, textPosition;
298: // look for any embedded exception text and remove it
299: if (messageText == null) {
300: messageText = "An application exception occurred, "
301: + remoteException.getClass() + ".";
302: } else {
303: // go thro' all of the texts which signify an embedded message
304: for (index = 0; index < EMBEDDED.length; ++index) {
305: if ((textPosition = messageText
306: .indexOf(EMBEDDED[index])) != -1) {
307: messageText = messageText.substring(textPosition
308: + EMBEDDED[index].length());
309: break;
310: }
311: }
312: }
313:
314: if (logger.isDebugEnabled()) {
315: logger
316: .debug("getRemoteMessage(Throwable) - end - return value = "
317: + messageText);
318: }
319: return messageText;
320: }
321:
322: /**
323: * <p>
324: * Private default constructor ensures utility class functionality.
325: * </p>
326: */
327: private ThrowableHandling() {
328: }
329: }
|