Home » xwork-2.1.5 » com.opensymphony » xwork2 » interceptor » [javadoc | source]

    1   /*
    2    * Copyright (c) 2002-2006 by OpenSymphony
    3    * All rights reserved.
    4    */
    5   package com.opensymphony.xwork2.interceptor;
    6   
    7   import com.opensymphony.xwork2.ActionInvocation;
    8   import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
    9   import com.opensymphony.xwork2.util.logging.Logger;
   10   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   11   
   12   import java.util.List;
   13   
   14   /**
   15    * <!-- START SNIPPET: description -->
   16    *
   17    * This interceptor forms the core functionality of the exception handling feature. Exception handling allows you to map
   18    * an exception to a result code, just as if the action returned a result code instead of throwing an unexpected
   19    * exception. When an exception is encountered, it is wrapped with an {@link ExceptionHolder} and pushed on the stack,
   20    * providing easy access to the exception from within your result.
   21    *
   22    * <b>Note:</b> While you can configure exception mapping in your configuration file at any point, the configuration
   23    * will not have any effect if this interceptor is not in the interceptor stack for your actions. It is recommended that
   24    * you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any
   25    * exception, even those caused by other interceptors.
   26    *
   27    * <!-- END SNIPPET: description -->
   28    *
   29    * <p/> <u>Interceptor parameters:</u>
   30    *
   31    * <!-- START SNIPPET: parameters -->
   32    *
   33    * <ul>
   34    *
   35    * <li>logEnabled (optional) - Should exceptions also be logged? (boolean true|false)</li>
   36    * 
   37    * <li>logLevel (optional) - what log level should we use (<code>trace, debug, info, warn, error, fatal</code>)? - defaut is <code>debug</code></li>
   38    * 
   39    * <li>logCategory (optional) - If provided we would use this category (eg. <code>com.mycompany.app</code>).
   40    * Default is to use <code>com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor</code>.</li>
   41    *
   42    * </ul>
   43    *
   44    * The parameters above enables us to log all thrown exceptions with stacktace in our own logfile,
   45    * and present a friendly webpage (with no stacktrace) to the end user.
   46    *
   47    * <!-- END SNIPPET: parameters -->
   48    *
   49    * <p/> <u>Extending the interceptor:</u>
   50    *
   51    * <p/>
   52    *
   53    * <!-- START SNIPPET: extending -->
   54    *
   55    * If you want to add custom handling for publishing the Exception, you may override
   56    * {@link #publishException(com.opensymphony.xwork2.ActionInvocation, ExceptionHolder)}. The default implementation
   57    * pushes the given ExceptionHolder on value stack. A custom implementation could add additional logging etc.
   58    *
   59    * <!-- END SNIPPET: extending -->
   60    *
   61    * <p/> <u>Example code:</u>
   62    *
   63    * <pre>
   64    * <!-- START SNIPPET: example -->
   65    * &lt;xwork&gt;
   66    *     &lt;package name="default" extends="xwork-default"&gt;
   67    *         &lt;global-results&gt;
   68    *             &lt;result name="error" type="freemarker"&gt;error.ftl&lt;/result&gt;
   69    *         &lt;/global-results&gt;
   70    *
   71    *         &lt;global-exception-mappings&gt;
   72    *             &lt;exception-mapping exception="java.lang.Exception" result="error"/&gt;
   73    *         &lt;/global-exception-mappings&gt;
   74    *
   75    *         &lt;action name="test"&gt;
   76    *             &lt;interceptor-ref name="exception"/&gt;
   77    *             &lt;interceptor-ref name="basicStack"/&gt;
   78    *             &lt;exception-mapping exception="com.acme.CustomException" result="custom_error"/&gt;
   79    *             &lt;result name="custom_error"&gt;custom_error.ftl&lt;/result&gt;
   80    *             &lt;result name="success" type="freemarker"&gt;test.ftl&lt;/result&gt;
   81    *         &lt;/action&gt;
   82    *     &lt;/package&gt;
   83    * &lt;/xwork&gt;
   84    * <!-- END SNIPPET: example -->
   85    * </pre>
   86    * 
   87    * <p/>
   88    * This second example will also log the exceptions using our own category
   89    * <code>com.mycompany.app.unhandled<code> at WARN level. 
   90    * 
   91    * <pre>
   92    * <!-- START SNIPPET: example2 -->
   93    * &lt;xwork&gt;
   94    *   &lt;package name="something" extends="xwork-default"&gt;
   95    *      &lt;interceptors&gt;
   96    *          &lt;interceptor-stack name="exceptionmappingStack"&gt;
   97    *              &lt;interceptor-ref name="exception"&gt;
   98    *                  &lt;param name="logEnabled"&gt;true&lt;/param&gt;
   99    *                  &lt;param name="logCategory"&gt;com.mycompany.app.unhandled&lt;/param&gt;
  100    *                  &lt;param name="logLevel"&gt;WARN&lt;/param&gt;	        		
  101    *              &lt;/interceptor-ref&gt;	
  102    *              &lt;interceptor-ref name="i18n"/&gt;
  103    *              &lt;interceptor-ref name="staticParams"/&gt;
  104    *              &lt;interceptor-ref name="params"/&gt;
  105    *              &lt;interceptor-ref name="validation"&gt;
  106    *                  &lt;param name="excludeMethods"&gt;input,back,cancel,browse&lt;/param&gt;
  107    *              &lt;/interceptor-ref&gt;
  108    *          &lt;/interceptor-stack&gt;
  109    *      &lt;/interceptors&gt;
  110    *
  111    *      &lt;default-interceptor-ref name="exceptionmappingStack"/&gt;
  112    *    
  113    *      &lt;global-results&gt;
  114    *           &lt;result name="unhandledException"&gt;/unhandled-exception.jsp&lt;/result&gt;
  115    *      &lt;/global-results&gt;
  116    *
  117    *      &lt;global-exception-mappings&gt;
  118    *           &lt;exception-mapping exception="java.lang.Exception" result="unhandledException"/&gt;
  119    *      &lt;/global-exception-mappings&gt;
  120    *        
  121    *      &lt;action name="exceptionDemo" class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction"&gt;
  122    *          &lt;exception-mapping exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
  123    *                             result="damm"/&gt;
  124    *          &lt;result name="input"&gt;index.jsp&lt;/result&gt;
  125    *          &lt;result name="success"&gt;success.jsp&lt;/result&gt;            
  126    *          &lt;result name="damm"&gt;damm.jsp&lt;/result&gt;
  127    *      &lt;/action&gt;
  128    *
  129    *   &lt;/package&gt;
  130    * &lt;/xwork&gt;
  131    * <!-- END SNIPPET: example2 -->
  132    * </pre>
  133    *
  134    * @author Matthew E. Porter (matthew dot porter at metissian dot com) 
  135    * @author Claus Ibsen
  136    */
  137   public class ExceptionMappingInterceptor extends AbstractInterceptor {
  138       
  139       protected static final Logger LOG = LoggerFactory.getLogger(ExceptionMappingInterceptor.class);
  140   
  141       protected Logger categoryLogger;
  142       protected boolean logEnabled = false;
  143       protected String logCategory;
  144       protected String logLevel;
  145       
  146   
  147       public boolean isLogEnabled() {
  148           return logEnabled;
  149       }
  150   
  151       public void setLogEnabled(boolean logEnabled) {
  152           this.logEnabled = logEnabled;
  153       }
  154   
  155       public String getLogCategory() {
  156   		return logCategory;
  157   	}
  158   
  159   	public void setLogCategory(String logCatgory) {
  160   		this.logCategory = logCatgory;
  161   	}
  162   
  163   	public String getLogLevel() {
  164   		return logLevel;
  165   	}
  166   
  167   	public void setLogLevel(String logLevel) {
  168   		this.logLevel = logLevel;
  169   	}
  170   
  171       @Override
  172       public String intercept(ActionInvocation invocation) throws Exception {
  173           String result;
  174   
  175           try {
  176               result = invocation.invoke();
  177           } catch (Exception e) {
  178               if (isLogEnabled()) {
  179                   handleLogging(e);
  180               }
  181               List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
  182               String mappedResult = this.findResultFromExceptions(exceptionMappings, e);
  183               if (mappedResult != null) {
  184                   result = mappedResult;
  185                   publishException(invocation, new ExceptionHolder(e));
  186               } else {
  187                   throw e;
  188               }
  189           }
  190   
  191           return result;
  192       }
  193   
  194       /**
  195        * Handles the logging of the exception.
  196        * 
  197        * @param e  the exception to log.
  198        */
  199       protected void handleLogging(Exception e) {
  200       	if (logCategory != null) {
  201           	if (categoryLogger == null) {
  202           		// init category logger
  203           		categoryLogger = LoggerFactory.getLogger(logCategory);
  204           	}
  205           	doLog(categoryLogger, e);
  206       	} else {
  207       		doLog(LOG, e);
  208       	}
  209       }
  210       
  211       /**
  212        * Performs the actual logging.
  213        * 
  214        * @param logger  the provided logger to use.
  215        * @param e  the exception to log.
  216        */
  217       protected void doLog(Logger logger, Exception e) {
  218       	if (logLevel == null) {
  219       		logger.debug(e.getMessage(), e);
  220       		return;
  221       	}
  222       	
  223       	if ("trace".equalsIgnoreCase(logLevel)) {
  224       		logger.trace(e.getMessage(), e);
  225       	} else if ("debug".equalsIgnoreCase(logLevel)) {
  226       		logger.debug(e.getMessage(), e);
  227       	} else if ("info".equalsIgnoreCase(logLevel)) {
  228       		logger.info(e.getMessage(), e);
  229       	} else if ("warn".equalsIgnoreCase(logLevel)) {
  230       		logger.warn(e.getMessage(), e);
  231       	} else if ("error".equalsIgnoreCase(logLevel)) {
  232       		logger.error(e.getMessage(), e);
  233       	} else if ("fatal".equalsIgnoreCase(logLevel)) {
  234       		logger.fatal(e.getMessage(), e);
  235       	} else {
  236       		throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
  237       	}
  238       }
  239   
  240       protected String findResultFromExceptions(List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
  241           String result = null;
  242   
  243           // Check for specific exception mappings.
  244           if (exceptionMappings != null) {
  245               int deepest = Integer.MAX_VALUE;
  246               for (Object exceptionMapping : exceptionMappings) {
  247                   ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
  248                   int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
  249                   if (depth >= 0 && depth < deepest) {
  250                       deepest = depth;
  251                       result = exceptionMappingConfig.getResult();
  252                   }
  253               }
  254           }
  255   
  256           return result;
  257       }
  258   
  259       /**
  260        * Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
  261        * Otherwise, returns depth. Lowest depth wins.
  262        *
  263        * @param exceptionMapping  the mapping classname
  264        * @param t  the cause
  265        * @return the depth, if not found -1 is returned.
  266        */
  267       public int getDepth(String exceptionMapping, Throwable t) {
  268           return getDepth(exceptionMapping, t.getClass(), 0);
  269       }
  270   
  271       private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
  272           if (exceptionClass.getName().contains(exceptionMapping)) {
  273               // Found it!
  274               return depth;
  275           }
  276           // If we've gone as far as we can go and haven't found it...
  277           if (exceptionClass.equals(Throwable.class)) {
  278               return -1;
  279           }
  280           return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
  281       }
  282   
  283       /**
  284        * Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
  285        * Subclasses may override this to customize publishing.
  286        *
  287        * @param invocation The invocation to publish Exception for.
  288        * @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
  289        */
  290       protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
  291           invocation.getStack().push(exceptionHolder);
  292       }
  293   }

Home » xwork-2.1.5 » com.opensymphony » xwork2 » interceptor » [javadoc | source]