com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.opensymphony.xwork2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.struts2.dispatcher.HttpParameters;
import java.util.List;
import java.util.Map;
/**
*
*
* This interceptor forms the core functionality of the exception handling feature. Exception handling allows you to map
* an exception to a result code, just as if the action returned a result code instead of throwing an unexpected
* exception. When an exception is encountered, it is wrapped with an {@link ExceptionHolder} and pushed on the stack,
* providing easy access to the exception from within your result.
*
*
*
* Note: While you can configure exception mapping in your configuration file at any point, the configuration
* will not have any effect if this interceptor is not in the interceptor stack for your actions. It is recommended that
* you make this interceptor the first interceptor on the stack, ensuring that it has full access to catch any
* exception, even those caused by other interceptors.
*
*
*
*
* Interceptor parameters:
*
*
*
*
*
* - logEnabled (optional) - Should exceptions also be logged? (boolean true|false)
*
* - logLevel (optional) - what log level should we use (
trace, debug, info, warn, error, fatal
)? - defaut is debug
*
* - logCategory (optional) - If provided we would use this category (eg.
com.mycompany.app
).
* Default is to use com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor
.
*
*
*
*
* The parameters above enables us to log all thrown exceptions with stacktace in our own logfile,
* and present a friendly webpage (with no stacktrace) to the end user.
*
*
*
*
* Extending the interceptor:
*
*
*
* If you want to add custom handling for publishing the Exception, you may override
* {@link #publishException(com.opensymphony.xwork2.ActionInvocation, ExceptionHolder)}. The default implementation
* pushes the given ExceptionHolder on value stack. A custom implementation could add additional logging etc.
*
*
*
* Example code:
*
*
*
* <xwork>
* <package name="default" extends="xwork-default">
* <global-results>
* <result name="error" type="freemarker">error.ftl</result>
* </global-results>
*
* <global-exception-mappings>
* <exception-mapping exception="java.lang.Exception" result="error"/>
* </global-exception-mappings>
*
* <action name="test">
* <interceptor-ref name="exception"/>
* <interceptor-ref name="basicStack"/>
* <exception-mapping exception="com.acme.CustomException" result="custom_error"/>
* <result name="custom_error">custom_error.ftl</result>
* <result name="success" type="freemarker">test.ftl</result>
* </action>
* </package>
* </xwork>
*
*
*
*
* This second example will also log the exceptions using our own category
* com.mycompany.app.unhandled
at WARN level.
*
*
*
*
* <xwork>
* <package name="something" extends="xwork-default">
* <interceptors>
* <interceptor-stack name="exceptionmappingStack">
* <interceptor-ref name="exception">
* <param name="logEnabled">true</param>
* <param name="logCategory">com.mycompany.app.unhandled</param>
* <param name="logLevel">WARN</param>
* </interceptor-ref>
* <interceptor-ref name="i18n"/>
* <interceptor-ref name="staticParams"/>
* <interceptor-ref name="params"/>
* <interceptor-ref name="validation">
* <param name="excludeMethods">input,back,cancel,browse</param>
* </interceptor-ref>
* </interceptor-stack>
* </interceptors>
*
* <default-interceptor-ref name="exceptionmappingStack"/>
*
* <global-results>
* <result name="unhandledException">/unhandled-exception.jsp</result>
* </global-results>
*
* <global-exception-mappings>
* <exception-mapping exception="java.lang.Exception" result="unhandledException"/>
* </global-exception-mappings>
*
* <action name="exceptionDemo" class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction">
* <exception-mapping exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
* result="damm"/>
* <result name="input">index.jsp</result>
* <result name="success">success.jsp</result>
* <result name="damm">damm.jsp</result>
* </action>
*
* </package>
* </xwork>
*
*
*
* @author Matthew E. Porter (matthew dot porter at metissian dot com)
* @author Claus Ibsen
*/
public class ExceptionMappingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
protected Logger categoryLogger;
protected boolean logEnabled = false;
protected String logCategory;
protected String logLevel;
public boolean isLogEnabled() {
return logEnabled;
}
public void setLogEnabled(boolean logEnabled) {
this.logEnabled = logEnabled;
}
public String getLogCategory() {
return logCategory;
}
public void setLogCategory(String logCatgory) {
this.logCategory = logCatgory;
}
public String getLogLevel() {
return logLevel;
}
public void setLogLevel(String logLevel) {
this.logLevel = logLevel;
}
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String result;
try {
result = invocation.invoke();
} catch (Exception e) {
if (isLogEnabled()) {
handleLogging(e);
}
List exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
if (mappingConfig != null && mappingConfig.getResult()!=null) {
Map mappingParams = mappingConfig.getParams();
// create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
HttpParameters parameters = HttpParameters.create(mappingParams).build();
invocation.getInvocationContext().setParameters(parameters);
result = mappingConfig.getResult();
publishException(invocation, new ExceptionHolder(e));
} else {
throw e;
}
}
return result;
}
/**
* Handles the logging of the exception.
*
* @param e the exception to log.
*/
protected void handleLogging(Exception e) {
if (logCategory != null) {
if (categoryLogger == null) {
// init category logger
categoryLogger = LogManager.getLogger(logCategory);
}
doLog(categoryLogger, e);
} else {
doLog(LOG, e);
}
}
/**
* Performs the actual logging.
*
* @param logger the provided logger to use.
* @param e the exception to log.
*/
protected void doLog(Logger logger, Exception e) {
if (logLevel == null) {
logger.debug(e.getMessage(), e);
return;
}
if ("trace".equalsIgnoreCase(logLevel)) {
logger.trace(e.getMessage(), e);
} else if ("debug".equalsIgnoreCase(logLevel)) {
logger.debug(e.getMessage(), e);
} else if ("info".equalsIgnoreCase(logLevel)) {
logger.info(e.getMessage(), e);
} else if ("warn".equalsIgnoreCase(logLevel)) {
logger.warn(e.getMessage(), e);
} else if ("error".equalsIgnoreCase(logLevel)) {
logger.error(e.getMessage(), e);
} else if ("fatal".equalsIgnoreCase(logLevel)) {
logger.fatal(e.getMessage(), e);
} else {
throw new IllegalArgumentException("LogLevel [" + logLevel + "] is not supported");
}
}
/**
* Try to find appropriate {@link ExceptionMappingConfig} based on provided Throwable
*
* @param exceptionMappings list of defined exception mappings
* @param t caught exception
* @return appropriate mapping or null
*/
protected ExceptionMappingConfig findMappingFromExceptions(List exceptionMappings, Throwable t) {
ExceptionMappingConfig config = null;
// Check for specific exception mappings.
if (exceptionMappings != null) {
int deepest = Integer.MAX_VALUE;
for (Object exceptionMapping : exceptionMappings) {
ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
if (depth >= 0 && depth < deepest) {
deepest = depth;
config = exceptionMappingConfig;
}
}
}
return config;
}
/**
* Return the depth to the superclass matching. 0 means ex matches exactly. Returns -1 if there's no match.
* Otherwise, returns depth. Lowest depth wins.
*
* @param exceptionMapping the mapping classname
* @param t the cause
* @return the depth, if not found -1 is returned.
*/
public int getDepth(String exceptionMapping, Throwable t) {
return getDepth(exceptionMapping, t.getClass(), 0);
}
private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
/**
* Default implementation to handle ExceptionHolder publishing. Pushes given ExceptionHolder on the stack.
* Subclasses may override this to customize publishing.
*
* @param invocation The invocation to publish Exception for.
* @param exceptionHolder The exceptionHolder wrapping the Exception to publish.
*/
protected void publishException(ActionInvocation invocation, ExceptionHolder exceptionHolder) {
invocation.getStack().push(exceptionHolder);
}
}