001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.netui.pageflow.internal;
020:
021: import org.apache.beehive.netui.util.internal.InternalStringBuilder;
022: import org.apache.beehive.netui.util.internal.ServletUtils;
023:
024: import org.apache.struts.action.ActionForward;
025: import org.apache.struts.action.ActionMapping;
026: import org.apache.struts.action.ActionForm;
027: import org.apache.struts.action.ExceptionHandler;
028: import org.apache.struts.action.ActionMessage;
029: import org.apache.struts.action.ActionMessages;
030: import org.apache.struts.Globals;
031: import org.apache.struts.util.RequestUtils;
032: import org.apache.struts.util.MessageResources;
033: import org.apache.struts.config.ModuleConfig;
034: import org.apache.struts.config.ExceptionConfig;
035:
036: import javax.servlet.http.HttpServletRequest;
037: import javax.servlet.http.HttpServletResponse;
038: import javax.servlet.ServletContext;
039: import javax.servlet.ServletException;
040: import javax.servlet.ServletRequest;
041: import javax.servlet.ServletResponse;
042: import javax.servlet.jsp.el.ELException;
043: import java.lang.reflect.InvocationTargetException;
044: import java.lang.reflect.UndeclaredThrowableException;
045: import java.lang.reflect.Method;
046: import java.lang.reflect.Modifier;
047: import java.util.Locale;
048: import java.util.Collection;
049: import java.util.Iterator;
050: import java.io.IOException;
051:
052: import org.apache.beehive.netui.pageflow.config.PageFlowExceptionConfig;
053: import org.apache.beehive.netui.pageflow.config.DelegatingExceptionConfig;
054: import org.apache.beehive.netui.pageflow.FlowController;
055: import org.apache.beehive.netui.pageflow.PageFlowController;
056: import org.apache.beehive.netui.pageflow.SharedFlowController;
057: import org.apache.beehive.netui.pageflow.PageFlowManagedObjectException;
058: import org.apache.beehive.netui.pageflow.PageFlowEventReporter;
059: import org.apache.beehive.netui.pageflow.ExpressionMessage;
060: import org.apache.beehive.netui.pageflow.PageFlowUtils;
061: import org.apache.beehive.netui.pageflow.FormData;
062: import org.apache.beehive.netui.pageflow.interceptor.InterceptorException;
063: import org.apache.beehive.netui.pageflow.handler.ExceptionsHandler;
064: import org.apache.beehive.netui.pageflow.handler.FlowControllerHandlerContext;
065: import org.apache.beehive.netui.pageflow.handler.ActionForwardHandler;
066: import org.apache.beehive.netui.pageflow.handler.Handlers;
067: import org.apache.beehive.netui.util.Bundle;
068: import org.apache.beehive.netui.util.internal.cache.ClassLevelCache;
069: import org.apache.beehive.netui.util.logging.Logger;
070:
071: public class DefaultExceptionsHandler extends DefaultHandler implements
072: ExceptionsHandler {
073: private static final Logger _log = Logger
074: .getInstance(DefaultExceptionsHandler.class);
075:
076: private static final String CACHEID_EXCEPTION_HANDLER_METHODS = "_netui:exceptionHandlers";
077:
078: private transient PageFlowEventReporter _eventReporter;
079:
080: public DefaultExceptionsHandler(ServletContext servletContext) {
081: init(null, null, servletContext);
082: _eventReporter = AdapterManager.getServletContainerAdapter(
083: servletContext).getEventReporter();
084: }
085:
086: public void reinit(ServletContext servletContext) {
087: super .reinit(servletContext);
088: _eventReporter = AdapterManager.getServletContainerAdapter(
089: servletContext).getEventReporter();
090: }
091:
092: public ActionForward handleException(
093: FlowControllerHandlerContext context, Throwable ex,
094: ActionMapping actionMapping, ActionForm form)
095: throws IOException, ServletException {
096: FlowController flowController = context.getFlowController();
097: ServletRequest request = context.getRequest();
098: ServletResponse response = context.getResponse();
099:
100: if (_log.isInfoEnabled()) {
101: _log.info("Handling Throwable " + ex.getClass().getName());
102: }
103:
104: //
105: // If we're already in the process of handling an exception, bail out.
106: //
107: PageFlowRequestWrapper rw = PageFlowRequestWrapper.get(context
108: .getRequest());
109: Throwable alreadyBeingHandled = rw.getExceptionBeingHandled();
110:
111: if (alreadyBeingHandled != null) {
112: if (_log.isDebugEnabled()) {
113: _log.debug("Already in the process of handling "
114: + alreadyBeingHandled.getClass().getName()
115: + "; bailing out of handling for "
116: + ex.getClass().getName());
117: }
118:
119: throw new UnhandledException(ex);
120: }
121:
122: rw.setExceptionBeingHandled(ex);
123:
124: // Keep track of the Struts module where we find the exception handler.
125: ModuleConfig moduleConfig = flowController.theModuleConfig();
126:
127: // Callback to the event reporter.
128: ActionMapping originalActionMapping = actionMapping;
129: _eventReporter.exceptionRaised(context, ex,
130: originalActionMapping, form, flowController);
131: long startTime = System.currentTimeMillis();
132:
133: //
134: // Look up the ExceptionConfig that's associated with this Throwable.
135: //
136: Class exClass = ex.getClass();
137: ExceptionConfig exceptionConfig = null;
138: if (actionMapping != null) {
139: exceptionConfig = actionMapping.findException(exClass);
140: } else {
141: // If the mapping was null (i.e., the exception happened before we got the action mapping), look for the
142: // exception only in the module config.
143: exceptionConfig = getExceptionConfig(exClass, moduleConfig);
144: }
145:
146: //
147: // If there was no applicable exception handler in the current ModuleConfig, look in a shared flow's module.
148: //
149: if (exceptionConfig == null) {
150: FlowController fallbackFC = getFallbackFlowController(
151: flowController, exClass, request, response,
152: getServletContext());
153:
154: if (fallbackFC != null) {
155: flowController = fallbackFC;
156: context = new FlowControllerHandlerContext(request,
157: response, flowController);
158: moduleConfig = flowController.theModuleConfig();
159: exceptionConfig = getExceptionConfig(exClass,
160: moduleConfig);
161:
162: if (exceptionConfig != null) {
163: // This is the module that will be handling the exception. Ensure that its message resources are
164: // initialized.
165: assert request instanceof HttpServletRequest : request
166: .getClass().getName();
167: InternalUtils.selectModule(
168: moduleConfig.getPrefix(),
169: (HttpServletRequest) request,
170: getServletContext());
171: rw.setCurrentFlowController(flowController);
172: }
173: }
174:
175: actionMapping = null; // This action mapping isn't relevant if we found the exception elsewhere.
176: }
177:
178: if (exceptionConfig != null) {
179: if (_log.isDebugEnabled()) {
180: _log.debug("Found exception-config for exception "
181: + exClass.getName() + ": handler="
182: + exceptionConfig.getHandler() + ", path="
183: + exceptionConfig.getPath());
184: }
185:
186: // If this is a delegating exception handler, use its *delegate's* ModuleConfig.
187: if (exceptionConfig instanceof DelegatingExceptionConfig) {
188: moduleConfig = ((DelegatingExceptionConfig) exceptionConfig)
189: .getDelegateModuleConfig(getServletContext());
190: }
191:
192: // First, see if it should be handled by invoking a handler method.
193: ActionForward ret = null;
194: if (exceptionConfig instanceof PageFlowExceptionConfig) {
195: PageFlowExceptionConfig pfExceptionConfig = (PageFlowExceptionConfig) exceptionConfig;
196:
197: if (pfExceptionConfig.isHandlerMethod()) {
198: ret = invokeExceptionHandlerMethod(context, ex,
199: pfExceptionConfig, form, actionMapping);
200: } else {
201: ret = invokeExceptionHandlerClass(context, ex,
202: pfExceptionConfig, actionMapping, form);
203: }
204: } else {
205: ret = invokeExceptionHandlerClass(context, ex,
206: exceptionConfig, actionMapping, form);
207: }
208:
209: ActionForwardHandler afh = Handlers
210: .get(getServletContext()).getActionForwardHandler();
211: String actionName = InternalUtils
212: .getActionName(actionMapping);
213: ret = afh.processForward(context, ret, actionMapping,
214: exceptionConfig, actionName, moduleConfig, form);
215:
216: // Callback to the event reporter.
217: long timeTaken = System.currentTimeMillis() - startTime;
218: _eventReporter.exceptionHandled(context, ex,
219: originalActionMapping, form, flowController, ret,
220: timeTaken);
221:
222: return ret;
223: }
224:
225: if (_log.isErrorEnabled()) {
226: InternalStringBuilder msg = new InternalStringBuilder(
227: "Throwable ").append(exClass.getName());
228: _log
229: .error(
230: msg
231: .append(
232: " unhandled by the current page flow (and any shared flow)")
233: .toString(), ex);
234: }
235:
236: if (!getRegisteredExceptionsHandler().eatUnhandledException(
237: context, ex)) {
238: // Throwing this ServletException derivative will prevent any outer try/catch blocks from re-processing
239: // the exception.
240: throw new UnhandledException(ex);
241: }
242:
243: return null;
244: }
245:
246: public Throwable unwrapException(
247: FlowControllerHandlerContext context, Throwable ex) {
248: if (ex instanceof InterceptorException) {
249: Throwable cause = ex.getCause();
250: if (cause != null)
251: return unwrapException(context, cause);
252: }
253:
254: //
255: // If the exception was thrown in a method we called through reflection, it will be an
256: // InvocationTargetException. Unwrap it. Do the same for the UndeclaredThrowable exceptions thrown when
257: // invoking methods through dynamic proxies.
258: //
259: if (ex instanceof InvocationTargetException) {
260: return unwrapException(context,
261: ((InvocationTargetException) ex)
262: .getTargetException());
263: }
264:
265: if (ex instanceof UndeclaredThrowableException) {
266: return unwrapException(context,
267: ((UndeclaredThrowableException) ex)
268: .getUndeclaredThrowable());
269: }
270:
271: if (ex instanceof ServletException) {
272: ServletException servletException = (ServletException) ex;
273: Throwable rootCause = servletException.getRootCause();
274: if (rootCause != null)
275: return unwrapException(context, rootCause);
276: }
277:
278: return ex;
279: }
280:
281: public void exposeException(FlowControllerHandlerContext context,
282: Throwable ex, ActionMapping actionMapping) {
283: //
284: // Put the exception in a place where Struts/NetUI tags will find it.
285: //
286: context.getRequest().setAttribute(Globals.EXCEPTION_KEY, ex);
287: }
288:
289: protected ExceptionConfig getExceptionConfig(Class exceptionType,
290: ModuleConfig moduleConfig) {
291: ExceptionConfig config = null;
292:
293: if (moduleConfig != null) {
294: while (config == null && exceptionType != null) {
295: config = moduleConfig.findExceptionConfig(exceptionType
296: .getName());
297:
298: // Loop again for our superclass (if any)
299: exceptionType = exceptionType.getSuperclass();
300: }
301: }
302:
303: return config;
304: }
305:
306: protected FlowController getFallbackFlowController(
307: FlowController originalFlowController, Class exClass,
308: ServletRequest request, ServletResponse response,
309: ServletContext servletContext) {
310: if (originalFlowController instanceof PageFlowController) {
311: Collection/*< SharedFlowController >*/sharedFlows = ((PageFlowController) originalFlowController)
312: .theSharedFlows().values();
313:
314: for (Iterator ii = sharedFlows.iterator(); ii.hasNext();) {
315: SharedFlowController sf = (SharedFlowController) ii
316: .next();
317: if (checkForExceptionConfig(sf, exClass, request))
318: return sf;
319: }
320: }
321:
322: assert request instanceof HttpServletRequest : request
323: .getClass().getName();
324: SharedFlowController globalApp = PageFlowUtils
325: .getGlobalApp((HttpServletRequest) request);
326: if (globalApp != null
327: && checkForExceptionConfig(globalApp, exClass, request))
328: return globalApp;
329: return null;
330: }
331:
332: private boolean checkForExceptionConfig(SharedFlowController sf,
333: Class exClass, ServletRequest request) {
334: ModuleConfig mc = sf.theModuleConfig();
335: ExceptionConfig ec = getExceptionConfig(exClass, mc);
336:
337: if (ec != null) {
338: if (_log.isDebugEnabled()) {
339: _log.debug("Found exception-config for "
340: + exClass.getName()
341: + " in SharedFlowController "
342: + sf.getDisplayName());
343: }
344:
345: InternalUtils.setCurrentModule(mc, request);
346: return true;
347: }
348:
349: return false;
350: }
351:
352: protected ActionForward invokeExceptionHandlerClass(
353: FlowControllerHandlerContext context, Throwable throwable,
354: ExceptionConfig exceptionConfig,
355: ActionMapping actionMapping, ActionForm form)
356: throws IOException, ServletException {
357: String handlerClassName = exceptionConfig.getHandler();
358:
359: try {
360: //
361: // Get the exception-handler class and delegate to it.
362: //
363: assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
364: assert context.getResponse() instanceof HttpServletResponse : "don't support ServletResponse currently.";
365: HttpServletRequest request = (HttpServletRequest) context
366: .getRequest();
367: HttpServletResponse response = (HttpServletResponse) context
368: .getResponse();
369: ExceptionHandler handler = (ExceptionHandler) RequestUtils
370: .applicationInstance(handlerClassName);
371: Exception ex = throwable instanceof Exception ? (Exception) throwable
372: : new Exception(throwable);
373: ActionForward result = handler.execute(ex, exceptionConfig,
374: actionMapping, form, request, response);
375:
376: if (_log.isDebugEnabled()) {
377: _log.debug("Exception-handler: forward to "
378: + result.getPath());
379: }
380:
381: return result;
382: } catch (ClassNotFoundException e) {
383: _log.error("Could not find exception-handler class "
384: + handlerClassName, e);
385: ServletUtils.throwServletException(e);
386: } catch (InstantiationException e) {
387: _log.error(
388: "Could not create instance of exception-handler class "
389: + handlerClassName, e);
390: ServletUtils.throwServletException(e);
391: } catch (IllegalAccessException e) {
392: _log.error(
393: "Could not create instance of exception-handler class "
394: + handlerClassName, e);
395: ServletUtils.throwServletException(e);
396: }
397:
398: assert false; // should not get here -- either a value is returned or an exception is thrown.
399: return null;
400: }
401:
402: protected ActionForward invokeExceptionHandlerMethod(
403: FlowControllerHandlerContext context, Throwable ex,
404: PageFlowExceptionConfig exceptionConfig, ActionForm form,
405: ActionMapping actionMapping) throws IOException,
406: ServletException {
407: assert context.getRequest() instanceof HttpServletRequest : "don't support ServletRequest currently.";
408: assert context.getResponse() instanceof HttpServletResponse : "don't support ServletResponse currently.";
409: HttpServletRequest request = (HttpServletRequest) context
410: .getRequest();
411: HttpServletResponse response = (HttpServletResponse) context
412: .getResponse();
413: FlowController flowController = context.getFlowController();
414: String methodName = exceptionConfig.getHandler();
415: Object unwrappedFormBean = InternalUtils.unwrapFormBean(form);
416: Method method = getExceptionHandlerMethod(context, methodName,
417: ex, unwrappedFormBean);
418:
419: if (method != null) {
420: // First see if there's a hard-coded message set.
421: String message = exceptionConfig.getDefaultMessage();
422: ActionMessage error = null;
423:
424: if (message != null) {
425: error = new ExpressionMessage(message,
426: new Object[] { ex.getMessage() });
427:
428: try {
429: // The message may be an expression. Evaluate it.
430: message = InternalExpressionUtils
431: .evaluateMessage(message, form, request,
432: getServletContext());
433: } catch (ELException e) {
434: _log.error(
435: "error while evaluating expression in exception-handler for "
436: + ex.getClass().getName(), e);
437: }
438: }
439:
440: if (message == null) {
441: // No hard-coded message. Get the message based on the message key.
442: String messageKey = exceptionConfig.getKey();
443:
444: if (messageKey != null && messageKey.length() > 0) {
445: message = getMessage(context, messageKey, null,
446: null);
447: }
448: }
449:
450: //
451: // Expose the exception to the errors tag.
452: //
453: String msgKey = exceptionConfig.getKey();
454: if (error == null)
455: error = new ActionMessage(msgKey, ex.getMessage());
456: storeException(request, msgKey, error, exceptionConfig
457: .getScope());
458:
459: return flowController.invokeExceptionHandler(method, ex,
460: message, form, exceptionConfig, actionMapping,
461: request, response);
462: } else {
463: //
464: // This shouldn't happen except in out-of-date-class situations. JpfChecker
465: // should prevent this at compilation time.
466: //
467: String err;
468: if (form != null) {
469: err = Bundle.getString(
470: "PageFlow_MissingExceptionHandlerWithForm",
471: new Object[] { methodName,
472: form.getClass().getName() });
473: } else {
474: err = Bundle.getString(
475: "PageFlow_MissingExceptionHandler", methodName);
476: }
477:
478: InternalUtils.sendError("PageFlow_Custom_Error", null,
479: request, response, new Object[] {
480: flowController.getDisplayName(), err });
481: return null;
482: }
483: }
484:
485: protected static void storeException(HttpServletRequest request,
486: String key, ActionMessage error, String scope) {
487: ActionMessages errors = new ActionMessages();
488: errors.add(key, error);
489:
490: if ("request".equals(scope)) {
491: request.setAttribute(Globals.ERROR_KEY, errors);
492: } else {
493: request.getSession()
494: .setAttribute(Globals.ERROR_KEY, errors);
495: }
496: }
497:
498: protected String getMessage(FlowControllerHandlerContext context,
499: String messageKey, String bundle, Object[] args) {
500: if (bundle == null)
501: bundle = Globals.MESSAGES_KEY;
502:
503: ServletRequest request = context.getRequest();
504: MessageResources resources = InternalUtils.getMessageResources(
505: bundle, request, getServletContext());
506:
507: if (resources == null) {
508: _log.error("Could not find message-resources for bundle "
509: + bundle);
510: return null;
511: }
512:
513: Locale userLocale = request instanceof HttpServletRequest ? FlowController
514: .retrieveUserLocale((HttpServletRequest) request, null)
515: : null;
516:
517: if (args == null) {
518: return resources.getMessage(userLocale, messageKey);
519: } else {
520: return resources.getMessage(userLocale, messageKey, args);
521: }
522: }
523:
524: public boolean eatUnhandledException(
525: FlowControllerHandlerContext context, Throwable ex) {
526: _log.error("Unhandled Page Flow Exception", ex);
527:
528: try {
529: //
530: // PageFlowExceptions know what to do in the unhandled case.
531: //
532: boolean prodMode = AdapterManager
533: .getServletContainerAdapter(getServletContext())
534: .isInProductionMode();
535:
536: if (!prodMode
537: && ex instanceof PageFlowManagedObjectException) {
538: ((PageFlowManagedObjectException) ex).sendError(context
539: .getRequest(), context.getResponse());
540: return true;
541: }
542: } catch (IOException ioEx) {
543: _log.error(ioEx.getMessage(), ioEx);
544: }
545:
546: return false;
547: }
548:
549: /**
550: * Get an Exception handler method.
551: *
552: * @param methodName the name of the method to get.
553: * @param ex the Exception that is to be handled.
554: * @return the Method with the given name that handles the given Exception, or <code>null</code>
555: * if none matches.
556: */
557: protected Method getExceptionHandlerMethod(
558: FlowControllerHandlerContext context, String methodName,
559: Throwable ex, Object formBean) {
560: FlowController flowController = context.getFlowController();
561: String cacheKey = methodName + '/' + ex.getClass().getName();
562: ClassLevelCache cache = ClassLevelCache.getCache(flowController
563: .getClass());
564: Method method = (Method) cache.get(
565: CACHEID_EXCEPTION_HANDLER_METHODS, cacheKey);
566:
567: if (method != null) {
568: return method;
569: }
570:
571: Class flowControllerClass = flowController.getClass();
572: for (Class exClass = ex.getClass(); exClass != null; exClass = exClass
573: .getSuperclass()) {
574: Class[] args = new Class[] { exClass, String.class,
575: String.class, Object.class };
576: Method foundMethod = InternalUtils.lookupMethod(
577: flowControllerClass, methodName, args);
578:
579: //
580: // If we didn't find an exception-handler with the right signature, look for the deprecated signature with
581: // FormData as the last argument.
582: //
583: if (foundMethod == null
584: && (formBean == null || formBean instanceof FormData)) {
585: args = new Class[] { exClass, String.class,
586: String.class, FormData.class };
587: foundMethod = InternalUtils.lookupMethod(
588: flowControllerClass, methodName, args);
589: }
590:
591: //
592: // If we didn't find an exception-handler with the right signature, look for the deprecated signature with
593: // ActionForm as the last argument.
594: //
595: if (foundMethod == null
596: && (formBean == null || formBean instanceof ActionForm)) {
597: args = new Class[] { exClass, String.class,
598: String.class, ActionForm.class };
599: foundMethod = InternalUtils.lookupMethod(
600: flowControllerClass, methodName, args);
601: }
602:
603: if (foundMethod != null) {
604: if (_log.isDebugEnabled()) {
605: _log.debug("Found exception handler for "
606: + exClass.getName());
607: }
608:
609: if (!Modifier.isPublic(foundMethod.getModifiers()))
610: foundMethod.setAccessible(true);
611: cache.put(CACHEID_EXCEPTION_HANDLER_METHODS, cacheKey,
612: foundMethod);
613: return foundMethod;
614: } else {
615: if (_log.isErrorEnabled()) {
616: InternalStringBuilder msg = new InternalStringBuilder(
617: "Could not find exception handler method ");
618: msg.append(methodName).append(" for ").append(
619: exClass.getName()).append('.');
620: _log.error(msg.toString());
621: }
622: }
623: }
624:
625: return null;
626: }
627:
628: public ExceptionsHandler getRegisteredExceptionsHandler() {
629: return (ExceptionsHandler) super.getRegisteredHandler();
630: }
631: }
|