001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.xwork.interceptor;
006:
007: import com.opensymphony.xwork.ActionInvocation;
008: import com.opensymphony.xwork.config.entities.ExceptionMappingConfig;
009: import org.apache.commons.logging.Log;
010: import org.apache.commons.logging.LogFactory;
011:
012: import java.util.Iterator;
013: import java.util.List;
014:
015: /**
016: * <!-- START SNIPPET: description -->
017: *
018: * This interceptor forms the core functionality of the exception handling feature. Exception handling allows you to map
019: * an exception to a result code, just as if the action returned a result code instead of throwing an unexpected
020: * exception. When an exception is encountered, it is wrapped with an {@link ExceptionHolder} and pushed on the stack,
021: * providing easy access to the exception from within your result.
022: *
023: * <b>Note:</b> While you can configure exception mapping in your configuration file at any point, the configuration
024: * will not have any effect if this interceptor is not in the interceptor stack for your actions. It is recommended that
025: * you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any
026: * exception, even those caused by other interceptors.
027: *
028: * <!-- END SNIPPET: description -->
029: *
030: * <p/> <u>Interceptor parameters:</u>
031: *
032: * <!-- START SNIPPET: parameters -->
033: *
034: * <ul>
035: *
036: * <li>logEnabled (optional) - Should exceptions also be logged? (boolean true|false)</li>
037: *
038: * <li>logLevel (optional) - what log level should we use (<code>trace, debug, info, warn, error, fatal</code>)? - defaut is <code>debug</code></li>
039: *
040: * <li>logCategory (optional) - If provided we would use this category (eg. <code>com.mycompany.app</code>).
041: * Default is to use <code>com.opensymphony.xwork.interceptor.ExceptionMappingInterceptor</code>.</li>
042: *
043: * </ul>
044: *
045: * The parameters above enables us to log all thrown exceptions with stacktace in our own logfile,
046: * and present a friendly webpage (with no stacktrace) to the end user.
047: *
048: * <!-- END SNIPPET: parameters -->
049: *
050: * <p/> <u>Extending the interceptor:</u>
051: *
052: * <p/>
053: *
054: * <!-- START SNIPPET: extending -->
055: *
056: * If you want to add custom handling for publishing the Exception, you may override
057: * {@link #publishException(com.opensymphony.xwork.ActionInvocation, ExceptionHolder)}. The default implementation
058: * pushes the given ExceptionHolder on value stack. A custom implementation could add additional logging etc.
059: *
060: * <!-- END SNIPPET: extending -->
061: *
062: * <p/> <u>Example code:</u>
063: *
064: * <pre>
065: * <!-- START SNIPPET: example -->
066: * <xwork>
067: * <include file="webwork-default.xml"/>
068: *
069: * <package name="default" extends="webwork-default">
070: * <global-results>
071: * <result name="error" type="freemarker">error.ftl</result>
072: * </global-results>
073: *
074: * <global-exception-mappings>
075: * <exception-mapping exception="java.lang.Exception" result="error"/>
076: * </global-exception-mappings>
077: *
078: * <action name="test">
079: * <interceptor-ref name="exception"/>
080: * <interceptor-ref name="basicStack"/>
081: * <exception-mapping exception="com.acme.CustomException" result="custom_error"/>
082: * <result name="custom_error">custom_error.ftl</result>
083: * <result name="success" type="freemarker">test.ftl</result>
084: * </action>
085: * </package>
086: * </xwork>
087: * <!-- END SNIPPET: example -->
088: * </pre>
089: *
090: * <p/>
091: * This second example will also log the exceptions using our own category
092: * <code>com.mycompany.app.unhandled<code> at WARN level.
093: *
094: * <pre>
095: * <!-- START SNIPPET: example2 -->
096: * <xwork>
097: * <include file="webwork-default.xml"/>
098: *
099: * <package name="something" extends="webwork-default">
100: * <interceptors>
101: * <interceptor-stack name="exceptionmapping-stack">
102: * <interceptor-ref name="exception">
103: * <param name="logEnabled">true</param>
104: * <param name="logCategory">com.mycompany.app.unhandled</param>
105: * <param name="logLevel">WARN</param>
106: * </interceptor-ref>
107: * <interceptor-ref name="i18n"/>
108: * <interceptor-ref name="static-params"/>
109: * <interceptor-ref name="params"/>
110: * <interceptor-ref name="validation">
111: * <param name="excludeMethods">input,back,cancel,browse</param>
112: * </interceptor-ref>
113: * </interceptor-stack>
114: * </interceptors>
115: *
116: * <default-interceptor-ref name="exceptionmapping-stack"/>
117: *
118: * <global-results>
119: * <result name="unhandledException">/unhandled-exception.jsp</result>
120: * </global-results>
121: *
122: * <global-exception-mappings>
123: * <exception-mapping exception="java.lang.Exception" result="unhandledException"/>
124: * </global-exception-mappings>
125: *
126: * <action name="exceptionDemo" class="com.opensymphony.webwork.showcase.exceptionmapping.ExceptionMappingAction">
127: * <exception-mapping exception="com.opensymphony.webwork.showcase.exceptionmapping.ExceptionMappingException"
128: * result="damm"/>
129: * <result name="input">index.jsp</result>
130: * <result name="success">success.jsp</result>
131: * <result name="damm">damm.jsp</result>
132: * </action>
133: *
134: * </package>
135: * </xwork>
136: * <!-- END SNIPPET: example2 -->
137: * </pre>
138: *
139: * @author Matthew E. Porter (matthew dot porter at metissian dot com)
140: * @author Claus Ibsen
141: */
142: public class ExceptionMappingInterceptor implements Interceptor {
143:
144: protected static final Log log = LogFactory
145: .getLog(ExceptionMappingInterceptor.class);
146:
147: protected Log categoryLogger;
148: protected boolean logEnabled = false;
149: protected String logCategory;
150: protected String logLevel;
151:
152: public boolean isLogEnabled() {
153: return logEnabled;
154: }
155:
156: public void setLogEnabled(boolean logEnabled) {
157: this .logEnabled = logEnabled;
158: }
159:
160: public String getLogCategory() {
161: return logCategory;
162: }
163:
164: public void setLogCategory(String logCatgory) {
165: this .logCategory = logCatgory;
166: }
167:
168: public String getLogLevel() {
169: return logLevel;
170: }
171:
172: public void setLogLevel(String logLevel) {
173: this .logLevel = logLevel;
174: }
175:
176: public void destroy() {
177: }
178:
179: public void init() {
180: }
181:
182: public String intercept(ActionInvocation invocation)
183: throws Exception {
184: String result;
185:
186: try {
187: result = invocation.invoke();
188: } catch (Exception e) {
189: if (logEnabled) {
190: handleLogging(e);
191: }
192: List exceptionMappings = invocation.getProxy().getConfig()
193: .getExceptionMappings();
194: String mappedResult = this .findResultFromExceptions(
195: exceptionMappings, e);
196: if (mappedResult != null) {
197: result = mappedResult;
198: publishException(invocation, new ExceptionHolder(e));
199: } else {
200: throw e;
201: }
202: }
203:
204: return result;
205: }
206:
207: /**
208: * Handles the logging of the exception.
209: *
210: * @param e the exception to log.
211: */
212: protected void handleLogging(Exception e) {
213: if (logCategory != null) {
214: if (categoryLogger == null) {
215: // init category logger
216: categoryLogger = LogFactory.getLog(logCategory);
217: }
218: doLog(categoryLogger, e);
219: } else {
220: doLog(log, e);
221: }
222: }
223:
224: /**
225: * Performs the actual logging.
226: *
227: * @param logger the provided logger to use.
228: * @param e the exception to log.
229: */
230: protected void doLog(Log logger, Exception e) {
231: if (logLevel == null) {
232: logger.debug(e.getMessage(), e);
233: return;
234: }
235:
236: if ("trace".equalsIgnoreCase(logLevel)) {
237: logger.trace(e.getMessage(), e);
238: } else if ("debug".equalsIgnoreCase(logLevel)) {
239: logger.debug(e.getMessage(), e);
240: } else if ("info".equalsIgnoreCase(logLevel)) {
241: logger.info(e.getMessage(), e);
242: } else if ("warn".equalsIgnoreCase(logLevel)) {
243: logger.warn(e.getMessage(), e);
244: } else if ("error".equalsIgnoreCase(logLevel)) {
245: logger.error(e.getMessage(), e);
246: } else if ("fatal".equalsIgnoreCase(logLevel)) {
247: logger.fatal(e.getMessage(), e);
248: } else {
249: throw new IllegalArgumentException("LogLevel [" + logLevel
250: + "] is not supported");
251: }
252: }
253:
254: private String findResultFromExceptions(List exceptionMappings,
255: Throwable t) {
256: String result = null;
257:
258: // Check for specific exception mappings.
259: if (exceptionMappings != null) {
260: int deepest = Integer.MAX_VALUE;
261: for (Iterator iter = exceptionMappings.iterator(); iter
262: .hasNext();) {
263: ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) iter
264: .next();
265: int depth = getDepth(exceptionMappingConfig
266: .getExceptionClassName(), t);
267: if (depth >= 0 && depth < deepest) {
268: deepest = depth;
269: result = exceptionMappingConfig.getResult();
270: }
271: }
272: }
273:
274: return result;
275: }
276:
277: /**
278: * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
279: * Otherwise, returns depth. Lowest depth wins.
280: *
281: * @param exceptionMapping the mapping classname
282: * @param t the cause
283: * @return the depth, if not found -1 is returned.
284: */
285: public int getDepth(String exceptionMapping, Throwable t) {
286: return getDepth(exceptionMapping, t.getClass(), 0);
287: }
288:
289: private int getDepth(String exceptionMapping, Class exceptionClass,
290: int depth) {
291: if (exceptionClass.getName().indexOf(exceptionMapping) != -1) {
292: // Found it!
293: return depth;
294: }
295: // If we've gone as far as we can go and haven't found it...
296: if (exceptionClass.equals(Throwable.class)) {
297: return -1;
298: }
299: return getDepth(exceptionMapping, exceptionClass
300: .getSuperclass(), depth + 1);
301: }
302:
303: /**
304: * Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
305: * Subclasses may override this to customize publishing.
306: *
307: * @param invocation The invocation to publish Exception for.
308: * @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
309: */
310: protected void publishException(ActionInvocation invocation,
311: ExceptionHolder exceptionHolder) {
312: invocation.getStack().push(exceptionHolder);
313: }
314: }
|