001: /* Copyright 2004 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.lang;
007:
008: import java.lang.reflect.Constructor;
009: import java.lang.reflect.InvocationTargetException;
010: import java.lang.reflect.Method;
011: import java.text.MessageFormat;
012: import java.util.ArrayList;
013: import java.util.List;
014: import java.util.Locale;
015: import java.util.NoSuchElementException;
016: import java.util.ResourceBundle;
017: import java.util.StringTokenizer;
018:
019: /**
020: * The <code>ThrowableHelper</code> class defines a set of utility
021: * methods for handling common error management operations in a
022: * fashion which takes advantage of the J2SDK 1.4 constructs while
023: * maintaining J2SDK 1.3 compatibility.
024: *
025: * @author <a href="mailto:jnielsen@sct.com">Jan Nielsen</a>
026: *
027: * @version "$Revision: 35982 $"
028: **/
029: public final class ThrowableHelper {
030: /** Statically configured internationalization token. */
031: private static final String I18N_TOKEN = Resources.getString(
032: ThrowableHelper.class, "i18n_token");
033:
034: /** Default throwable handler. */
035: private static final ThrowableHandler DEFAULT_HANDLER = getDefaultHandler(Resources
036: .getString(ThrowableHelper.class,
037: "default_throwable_handler_class"));
038:
039: /**
040: * Returns a constructed instance of the default handler. The
041: * class name passed to the method must be an implementation of
042: * the <code>ThrowableHandler</code> interface and have an
043: * accessible default constructor.
044: *
045: * @param className name of the throwable handler class
046: *
047: * @return instance of the specified handler class, or the default
048: * error handler
049: **/
050: public static final ThrowableHandler getDefaultHandler(
051: String className) {
052: ThrowableHandler handler = new ThrowableHandler() {
053: /**
054: * Handles the condition specified in the
055: * parameters. The handler can use the client class to
056: * resolve the property name of the error message, and
057: * generate a localized message from the optional
058: * objects.
059: *
060: * @param client client calling the handle method
061: *
062: * @param property property name associated with error
063: * message
064: *
065: * @param objects objects associated with the error
066: * message, or <code>null</code>
067: *
068: * @param cause throwable condition which caused the
069: * error, or <code>null</code>
070: *
071: * @throws NullPointerException if client or property
072: * is <code>null</code>
073: **/
074: public void handle(Class client, String property,
075: String[] objects, Throwable cause) {
076: if (null != property) {
077: String errorMessage = Resources.getString(client,
078: property, objects);
079:
080: System.err.println(errorMessage);
081: }
082:
083: if (null != cause) {
084: cause.printStackTrace();
085: }
086: }
087: };
088:
089: try {
090: if (null != className && !"".equals(className)) {
091: handler = (ThrowableHandler) Class.forName(className)
092: .newInstance();
093: }
094: } catch (ClassNotFoundException x) {
095: ThrowableHelper.handle(ThrowableHelper.class,
096: "error.class_not_found",
097: new String[] { className }, x, handler);
098: } catch (IllegalAccessException x) {
099: ThrowableHelper.handle(ThrowableHelper.class,
100: "error.illegal_access", new String[] { className },
101: x, handler);
102: } catch (InstantiationException x) {
103: ThrowableHelper.handle(ThrowableHelper.class,
104: "error.instantiation", new String[] { className },
105: x, handler);
106: } catch (ExceptionInInitializerError x) {
107: ThrowableHelper.handle(ThrowableHelper.class,
108: "error.initialization", new String[] { className },
109: x, handler);
110: }
111:
112: return handler;
113: }
114:
115: /**
116: * Returns an internationalized error message which can be
117: * reconstructed into a localized error message with the
118: * <code>getLocalizedMessage</code> method call.
119: *
120: * @param client class of the client making this call
121: *
122: * @param error error property name of the error condition
123: *
124: * @return internationalized error message
125: **/
126: public static final String getInternationalizedMessage(
127: Class client, String error) {
128: return getInternationalizedMessage(client, error, null);
129: }
130:
131: /**
132: * Returns an internationalized error message which can be
133: * reconstructed into a localized error message with the
134: * <code>getLocalizedMessage</code> method call.
135: *
136: * @param client class of the client making this call
137: *
138: * @param error error property name of the error condition
139: *
140: * @param objects string objects which should be stored for the
141: * message
142: *
143: * @return internationalized error message
144: *
145: * @throws NullPointerException if client, or error parameters are
146: * null
147: **/
148: public static final String getInternationalizedMessage(
149: Class client, String error, String[] objects) {
150: if (null == client) {
151: throw new NullPointerException(
152: ThrowableHelper
153: .getInternationalizedMessage(
154: ThrowableHelper.class,
155: "getInternationalizedMessage.client_argument_is_null"));
156: }
157:
158: if (null == error) {
159: throw new NullPointerException(
160: ThrowableHelper
161: .getInternationalizedMessage(
162: ThrowableHelper.class,
163: "getInternationalizedMessage.error_argument_is_null"));
164: }
165:
166: StringBuffer buffer = new StringBuffer().append(I18N_TOKEN)
167: .append(client.getName()).append(I18N_TOKEN).append(
168: error);
169:
170: if (null != objects) {
171: for (int i = 0; i < objects.length; i++) {
172: buffer.append(I18N_TOKEN).append(objects[i]);
173: }
174: }
175:
176: return buffer.toString();
177: }
178:
179: /**
180: * Returns the localized error message associated with the
181: * specified internationalized error message constructed with the
182: * <code>getInternationalizedMessage</code> method. The default
183: * locale is used.
184: *
185: * @param i18nMessage internationalized error message
186: *
187: * @return localized error message, or <code>null</code>
188: **/
189: public static final String getLocalizedMessage(String i18nMessage) {
190: return getLocalizedMessage(i18nMessage, Locale.getDefault());
191: }
192:
193: /**
194: * Returns the localized string associated with the argument.
195: *
196: * @param throwable object whose message is to be localized
197: *
198: * @return localized message associated with <code>Throwable</code>
199: **/
200: public static final String getLocalizedMessage(Throwable throwable) {
201: return getLocalizedMessage(throwable, Locale.getDefault());
202: }
203:
204: /**
205: * Returns the localized string associated with the argument
206: * in the specified locale, if possible. If the locale is not
207: * supported the default locale message will be returned.
208: *
209: * @param throwable object whose message is to be localized
210: *
211: * @param locale locale to localized the message
212: *
213: * @return localized message associated with <code>Throwable</code>
214: **/
215: public static final String getLocalizedMessage(Throwable throwable,
216: Locale locale) {
217: return getLocalizedMessage(throwable.getMessage(), locale);
218: }
219:
220: /**
221: * Returns the localized error message associated with the
222: * specified internationalized error message constructed with the
223: * <code>getInternationalizedMessage</code> method. The locale of
224: * for the translation is picked up from the current session.
225: *
226: * @param i18nMessage internationalized error message
227: *
228: * @param locale locale to translate the message
229: *
230: * @return localized error message, or <code>null</code>
231: **/
232: public static final String getLocalizedMessage(String i18nMessage,
233: Locale locale) {
234: String l10nMessage = null;
235:
236: if (null != i18nMessage) {
237: try {
238: // if the i18nMessage starts with a I18N_TOKEN, the
239: // tokenizer's nextToken returns the next token
240: // instead of null, so add some data to make sure this
241: // skip does not happen automatically
242: StringTokenizer tokenizer = new StringTokenizer("junk"
243: + i18nMessage, I18N_TOKEN);
244:
245: // now skip the "junk" token
246: tokenizer.nextToken();
247:
248: String className = tokenizer.nextToken();
249: String errorName = tokenizer.nextToken();
250:
251: List strings = new ArrayList();
252:
253: while (tokenizer.hasMoreTokens()) {
254: strings.add(tokenizer.nextToken());
255: }
256:
257: String[] objects = new String[strings.size()];
258: for (int i = 0; i < objects.length; i++) {
259: objects[i] = (String) strings.get(i);
260: }
261:
262: ResourceBundle bundle = ResourceBundle.getBundle(
263: className, locale);
264:
265: String message = bundle.getString(errorName);
266:
267: l10nMessage = MessageFormat.format(message,
268: (Object[]) objects);
269: } catch (NoSuchElementException x) {
270: // Not a well-formed internationalized string.
271: l10nMessage = i18nMessage;
272: }
273: }
274:
275: return l10nMessage;
276: }
277:
278: /**
279: * Initizlizes the chained cause of the throwable if possible
280: * using the J2SDK 1.4 constructs. In a J2SDK pre-1.4 environment,
281: * this method does nothing.
282: *
283: * @param throwable throwable whose cause should be set
284: *
285: * @param cause throwable which caused the throwable condition
286: **/
287: public static final void initCause(Throwable throwable,
288: Throwable cause) {
289: try {
290: Method initCause = throwable.getClass().getMethod(
291: "initCause", new Class[] { Throwable.class });
292:
293: initCause.invoke(throwable, new Object[] { cause });
294: } catch (NoSuchMethodException x) {
295: ThrowableHelper.handle(x);
296: } catch (InvocationTargetException x) {
297: ThrowableHelper.handle(x);
298: } catch (IllegalAccessException x) {
299: ThrowableHelper.handle(x);
300: } catch (IllegalStateException x) {
301: ThrowableHelper.handle(x);
302: }
303: }
304:
305: /**
306: * Creates the specified <code>Throwable</code> object with the
307: * internationalized error code and arguments. If the cause object
308: * is not null, the specified object's <code>initCause</code>
309: * method will be invoked reflectively to enable the standard
310: * chaining constructs. The specified error code is an
311: * internationalized error property value which must be defined in
312: * a resource bundle associated with the client, specificifically
313: * the code will perform a <code>ResourceBundle.getBundle( <name
314: * of client> )</code> to resolve the resource.
315: *
316: * Invoking this method is equivalent to creating the
317: * <code>Throwable</code> with an internationalized message and
318: * then invoking <code>ThrowableHelper.initCause</code>, e.g.,:
319: *
320: * <code><pre>
321: * Throwable someCause = ...
322: *
323: * String i18nMessage = ThrowableHelper.getInternationalizedMessage(
324: * MyApplication.class,
325: * "myapplication.some_error_code",
326: * new String[]{ "32", "1024" }
327: * );
328: *
329: * IllegalArgumentException iae = new IllegalArgumentException(
330: * i18nMessage
331: * );
332: *
333: * ThrowableHelper.initCause(
334: * iae,
335: * someCause
336: * );
337: * </pre></code>
338: *
339: * @param throwableClass throwable object to be constructed
340: *
341: * @param client class file of the client
342: *
343: * @param propertyName internationalized error code property name
344: * defined in the client's resouce bundle
345: *
346: * @param args arguments for the localized message
347: *
348: * @param cause cause of the created exception, or
349: * <code>null</code>
350: *
351: * @return throwable object of the specified type
352: **/
353: public static final Throwable create(Class throwableClass,
354: Class client, String propertyName, String[] args,
355: Throwable cause) {
356: Throwable throwable = null;
357:
358: String i18nMessage = getInternationalizedMessage(client,
359: propertyName, args);
360:
361: throwable = createThrowable(throwableClass,
362: new Class[] { String.class },
363: new Object[] { i18nMessage });
364:
365: if (null != cause) {
366: ThrowableHelper.initCause(throwable, cause);
367: }
368:
369: return throwable.fillInStackTrace();
370: }
371:
372: /**
373: * Creates a <code>Throwable</code> object of the specified type
374: * using a constructor matching the specified arguments. The
375: * constructed object is returned. It is considered a static
376: * programming error if the requested object cannot be constructed
377: * with the specified parameters, so checking for
378: * <code>null</code> in the return is inappropriate; instead,
379: * validate that the specified <code>Throwable</code> class can be
380: * constructed in the specified manner by, for example, building
381: * unit test code which performs the same construction..
382: *
383: * @param throwableClass class to construct
384: *
385: * @param clsArguments constructor argument class types
386: *
387: * @param objArguments constructor argument object values
388: *
389: * @return <code>Throwable</code> object of the specified type, or
390: * <code>null</code>
391: **/
392: private static final Throwable createThrowable(
393: Class throwableClass, Class[] clsArguments,
394: Object[] objArguments) {
395: Throwable throwable = null;
396:
397: try {
398: Constructor ctor = throwableClass
399: .getConstructor(clsArguments);
400:
401: throwable = (Throwable) ctor.newInstance(objArguments);
402: } catch (NoSuchMethodException x) {
403: ThrowableHelper.handle(x);
404: } catch (IllegalAccessException x) {
405: ThrowableHelper.handle(x);
406: } catch (InstantiationException x) {
407: ThrowableHelper.handle(x);
408: } catch (InvocationTargetException x) {
409: ThrowableHelper.handle(x);
410: }
411:
412: return throwable;
413: }
414:
415: /**
416: * Handles the throwable condition specified in the parameter. The
417: * default handler is used to process this handler.
418: *
419: * @param cause throwable condition which should be handled
420: **/
421: public static final void handle(Throwable cause) {
422: ThrowableHelper
423: .handle(null, null, null, cause, DEFAULT_HANDLER);
424: }
425:
426: /**
427: * Handles the throwable condition specified in the
428: * parameters. The default handler is used to process this
429: * handler.
430: *
431: * @param client client performing the handling
432: *
433: * @param cause throwable condition which should be handled
434: *
435: * @param message associated with handling the error
436: *
437: * @param objects associated with the error message
438: **/
439: public static final void handle(Class client, String message,
440: String[] objects, Throwable cause) {
441: ThrowableHelper.handle(client, message, objects, cause,
442: DEFAULT_HANDLER);
443: }
444:
445: /**
446: * Handles the throwable condition specified in the parameters. If
447: * the specified handler is <code>null</code> the default handler
448: * is used; otherwise, the specified handler is used to process
449: * this request.
450: *
451: * @param client client performing the handling
452: *
453: * @param cause throwable condition which should be handled
454: *
455: * @param message associated with handling the error
456: *
457: * @param objects associated with the error message
458: *
459: * @param handler which should be executed instead of the default
460: * handler
461: **/
462: public static final void handle(Class client, String message,
463: String[] objects, Throwable cause, ThrowableHandler handler) {
464: ThrowableHandler errorHandler = handler;
465:
466: if (null == errorHandler) {
467: errorHandler = DEFAULT_HANDLER;
468: }
469:
470: errorHandler.handle(client, message, objects, cause);
471: }
472:
473: /**
474: * Private constructor.
475: **/
476: private ThrowableHelper() {
477: }
478: }
|