/*
* Copyright (c) 2002-2012 Alibaba Group Holding Limited.
* All rights reserved.
*
* Licensed 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.alibaba.citrus.turbine.pipeline.valve;
import static com.alibaba.citrus.springext.util.DomUtil.*;
import static com.alibaba.citrus.springext.util.SpringExtUtil.*;
import static com.alibaba.citrus.turbine.util.TurbineUtil.*;
import static com.alibaba.citrus.util.Assert.*;
import static com.alibaba.citrus.util.CollectionUtil.*;
import static com.alibaba.citrus.util.ExceptionUtil.*;
import static com.alibaba.citrus.util.StringUtil.*;
import static com.alibaba.citrus.webx.util.ErrorHandlerHelper.LoggingDetail.*;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.citrus.service.pipeline.PipelineContext;
import com.alibaba.citrus.service.pipeline.support.AbstractValve;
import com.alibaba.citrus.springext.support.parser.AbstractSingleBeanDefinitionParser;
import com.alibaba.citrus.turbine.TurbineRunDataInternal;
import com.alibaba.citrus.webx.util.ErrorHandlerHelper;
import com.alibaba.citrus.webx.util.ErrorHandlerHelper.LoggingDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* 在<code><pipeline id="exceptionPipeline"></code>中,处理异常的valve。
*
* @author Michael Zhou
*/
public class HandleExceptionValve extends AbstractValve {
private final static Logger log = LoggerFactory.getLogger(HandleExceptionValve.class);
private final static String HELPER_NAME_DEFAULT = "error";
private final HttpServletRequest request;
private String defaultPage;
private ExceptionHandler[] exceptionHandlers;
private String helperName;
private LoggingDetail defaultLoggingDetail;
private Logger defaultLogger;
public HandleExceptionValve(HttpServletRequest request) {
this.request = assertNotNull(assertProxy(request), "no request");
}
public void setDefaultPage(String defaultPage) {
this.defaultPage = trimToNull(defaultPage);
}
public void setDefaultLogging(LoggingDetail defaultLoggingDetail) {
this.defaultLoggingDetail = defaultLoggingDetail;
}
public void setDefaultLoggerName(String defaultLoggerName) {
defaultLoggerName = trimToNull(defaultLoggerName);
if (defaultLoggerName != null) {
this.defaultLogger = LoggerFactory.getLogger(defaultLoggerName);
}
}
public void setExceptionHandlers(ExceptionHandler[] exceptionHandlers) {
this.exceptionHandlers = exceptionHandlers;
}
public void setHelperName(String helperName) {
this.helperName = trimToNull(helperName);
}
@Override
protected void init() throws Exception {
assertNotNull(defaultPage, "no defaultPage");
if (exceptionHandlers == null) {
exceptionHandlers = new ExceptionHandler[0];
}
if (defaultLoggingDetail == null) {
defaultLoggingDetail = detailed;
}
if (defaultLogger == null) {
defaultLogger = log;
}
// 用default值初始化exception handlers
for (ExceptionHandler handler : exceptionHandlers) {
handler.init(defaultLoggingDetail, defaultLogger);
}
// 按exception排序,将子类排在前
exceptionHandlers = sortExceptions(exceptionHandlers);
if (helperName == null) {
helperName = HELPER_NAME_DEFAULT;
}
}
private ExceptionHandler[] sortExceptions(ExceptionHandler[] handlers) {
Set<ExceptionHandler> visited = createHashSet();
List<ExceptionHandler> sorted = createLinkedList();
for (ExceptionHandler handler : handlers) {
visitException(handler, handlers, visited, sorted);
}
return sorted.toArray(new ExceptionHandler[sorted.size()]);
}
private void visitException(ExceptionHandler handler, ExceptionHandler[] handlers, Set<ExceptionHandler> visited,
List<ExceptionHandler> sorted) {
if (visited.contains(handler)) {
return;
}
visited.add(handler);
for (ExceptionHandler test : handlers) {
if (handler.getExceptionType().isAssignableFrom(test.getExceptionType())) {
visitException(test, handlers, visited, sorted);
}
}
sorted.add(handler);
}
public void invoke(PipelineContext pipelineContext) throws Exception {
TurbineRunDataInternal rundata = (TurbineRunDataInternal) getTurbineRunData(request);
ErrorHandlerHelper helper = ErrorHandlerHelper.getInstance(request);
Throwable exception = helper.getException();
// 模板中可用的helper
rundata.getContext().put(helperName, helper);
if (exception != null) {
int statusCode = -1;
String target = null;
LoggingDetail loggingDetail = defaultLoggingDetail;
Logger logger = defaultLogger;
// 从最根本的exception cause开始反向追溯,例如:t1 caused by t2 caused by t3,
// 那么,检查顺序为t3, t2, t1。
CAUSES:
for (Throwable cause : getCauses(exception, true)) {
// 对于每个异常,查找匹配的exception handlers。
// 所有handlers已经排序,较特殊的异常在前,假设T1 extends T2,那么T1在T2之前。
for (ExceptionHandler exceptionHandler : exceptionHandlers) {
if (exceptionHandler.getExceptionType().isInstance(cause)) {
statusCode = exceptionHandler.getStatusCode();
target = exceptionHandler.getPage();
loggingDetail = exceptionHandler.getLoggingDetail();
logger = exceptionHandler.getLogger();
break CAUSES;
}
}
}
if (statusCode > 0) {
rundata.getResponse().setStatus(statusCode);
helper.setStatusCode(statusCode); // 更新request attributes
}
// 打印日志
helper.logError(logger, loggingDetail);
// 设定错误页面target
if (target == null) {
target = defaultPage;
}
rundata.setTarget(target);
}
// 执行下一个Valve
pipelineContext.invokeNext();
}
public static class ExceptionHandler {
private final Class<? extends Throwable> exceptionType;
private final int statusCode;
private final String page;
private LoggingDetail loggingDetail;
private Logger logger;
public ExceptionHandler(Class<? extends Throwable> exceptionType, int statusCode, String page, LoggingDetail loggingDetail, String loggerName) {
this.exceptionType = assertNotNull(exceptionType, "no exception type");
this.statusCode = statusCode <= 0 ? -1 : statusCode;
this.page = trimToNull(page);
this.loggingDetail = loggingDetail;
loggerName = trimToNull(loggerName);
if (loggerName != null) {
this.logger = LoggerFactory.getLogger(loggerName);
}
}
private void init(LoggingDetail defaultLoggingDetail, Logger defaultLogger) {
if (logger == null) {
logger = defaultLogger;
}
if (loggingDetail == null) {
loggingDetail = defaultLoggingDetail;
}
}
public Class<? extends Throwable> getExceptionType() {
return exceptionType;
}
public int getStatusCode() {
return statusCode;
}
public String getPage() {
return page;
}
public LoggingDetail getLoggingDetail() {
return loggingDetail;
}
public Logger getLogger() {
return logger;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append("on ").append(exceptionType.getName());
if (page != null) {
buf.append(", go to page ").append(page);
}
if (statusCode > 0) {
buf.append(", with status code ").append(statusCode);
}
buf.append(", with ").append(loggingDetail).append(" logging");
return buf.toString();
}
}
public static class DefinitionParser extends AbstractSingleBeanDefinitionParser<HandleExceptionValve> {
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
attributesToProperties(element, builder, "defaultPage", "defaultLogging", "defaultLoggerName", "helperName");
addConstructorArg(builder, true, HttpServletRequest.class);
List<Object> exceptionHandlers = createManagedList(element, parserContext);
for (Element onExceptionElement : subElements(element, and(sameNs(element), name("on-exception")))) {
exceptionHandlers.add(doParseOnException(onExceptionElement, parserContext, builder));
}
builder.addPropertyValue("exceptionHandlers", exceptionHandlers);
}
private Object doParseOnException(Element onExceptionElement, ParserContext parserContext,
BeanDefinitionBuilder builder) {
BeanDefinitionBuilder onExceptionBuilder = BeanDefinitionBuilder
.genericBeanDefinition(ExceptionHandler.class);
onExceptionBuilder.addConstructorArgValue(onExceptionElement.getAttribute("type"));
if (onExceptionElement.hasAttribute("statusCode")) {
onExceptionBuilder.addConstructorArgValue(onExceptionElement.getAttribute("statusCode"));
} else {
onExceptionBuilder.addConstructorArgValue("-1");
}
onExceptionBuilder.addConstructorArgValue(onExceptionElement.getAttribute("page"));
onExceptionBuilder.addConstructorArgValue(trimToNull(onExceptionElement.getAttribute("logging")));
onExceptionBuilder.addConstructorArgValue(onExceptionElement.getAttribute("loggerName"));
return onExceptionBuilder.getBeanDefinition();
}
}
}