org.springframework.webflow.engine.support.TransitionExecutingStateExceptionHandler Maven / Gradle / Ivy
/*
* Copyright 2002-2006 the original author or authors.
*
* 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 org.springframework.webflow.engine.support;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.JdkVersion;
import org.springframework.core.NestedRuntimeException;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.webflow.engine.ActionList;
import org.springframework.webflow.engine.FlowExecutionExceptionHandler;
import org.springframework.webflow.engine.RequestControlContext;
import org.springframework.webflow.engine.TargetStateResolver;
import org.springframework.webflow.engine.Transition;
import org.springframework.webflow.execution.FlowExecutionException;
import org.springframework.webflow.execution.ViewSelection;
/**
* A flow execution exception handler that maps the occurence of a specific type of
* exception to a transition to a new {@link org.springframework.webflow.engine.State}.
*
* @author Keith Donald
*/
public class TransitionExecutingStateExceptionHandler implements FlowExecutionExceptionHandler {
private static final Log logger = LogFactory.getLog(TransitionExecutingStateExceptionHandler.class);
/**
* The name of the attribute to expose a handled exception under in
* flash scope.
*/
public static final String STATE_EXCEPTION_ATTRIBUTE = "stateException";
/**
* The name of the attribute to expose a root cause of a handled exception
* under in flash scope.
*/
public static final String ROOT_CAUSE_EXCEPTION_ATTRIBUTE = "rootCauseException";
/**
* The exceptionType->targetStateResolver map.
*/
private Map exceptionTargetStateMappings = new HashMap();
/**
* The list of actions to execute when this handler handles an exception.
*/
private ActionList actionList = new ActionList();
/**
* Adds an exception->state mapping to this handler.
* @param exceptionClass the type of exception to map
* @param targetStateId the id of the state to transition to if the
* specified type of exception is handled
* @return this handler, to allow for adding multiple mappings in a single
* statement
*/
public TransitionExecutingStateExceptionHandler add(Class exceptionClass, String targetStateId) {
return add(exceptionClass, new DefaultTargetStateResolver(targetStateId));
}
/**
* Adds a exception->state mapping to this handler.
* @param exceptionClass the type of exception to map
* @param targetStateResolver the resolver to calculate the state to
* transition to if the specified type of exception is handled
* @return this handler, to allow for adding multiple mappings in a single
* statement
*/
public TransitionExecutingStateExceptionHandler add(Class exceptionClass, TargetStateResolver targetStateResolver) {
Assert.notNull(exceptionClass, "The exception class is required");
Assert.notNull(targetStateResolver, "The target state resolver is required");
exceptionTargetStateMappings.put(exceptionClass, targetStateResolver);
return this;
}
/**
* Returns the list of actions to execute when this handler handles an exception.
* The returned list is mutable.
*/
public ActionList getActionList() {
return actionList;
}
public boolean handles(FlowExecutionException e) {
return getTargetStateResolver(e) != null;
}
public ViewSelection handle(FlowExecutionException e, RequestControlContext context) {
if (logger.isDebugEnabled()) {
logger.debug("Handling state exception " + e);
}
// expose state exception in flash scope so it's available for response rendering
context.getFlashScope().put(STATE_EXCEPTION_ATTRIBUTE, e);
Throwable rootCause = findRootCause(e);
if (logger.isDebugEnabled()) {
logger.debug("Exposing state exception root cause " + rootCause + " under attribute '"
+ ROOT_CAUSE_EXCEPTION_ATTRIBUTE + "'");
}
// expose root cause in flash scope so it's available for response rendering
context.getFlashScope().put(ROOT_CAUSE_EXCEPTION_ATTRIBUTE, rootCause);
actionList.execute(context);
return context.execute(new Transition(getTargetStateResolver(e)));
}
// helpers
/**
* Find the mapped target state resolver for given exception. Returns
* null
if no mapping can be found for given exception. Will
* try all exceptions in the exception cause chain.
*/
protected TargetStateResolver getTargetStateResolver(FlowExecutionException e) {
if (JdkVersion.getMajorJavaVersion() == JdkVersion.JAVA_13) {
return getTargetStateResolver13(e);
}
else {
return getTargetStateResolver14(e);
}
}
/**
* Internal getTargetStateResolver implementation for use with JDK 1.3.
*/
private TargetStateResolver getTargetStateResolver13(NestedRuntimeException e) {
TargetStateResolver targetStateResolver;
if (isRootCause13(e)) {
return findTargetStateResolver(e.getClass());
}
else {
targetStateResolver = (TargetStateResolver)exceptionTargetStateMappings.get(e.getClass());
if (targetStateResolver != null) {
return targetStateResolver;
}
else {
if (e.getCause() instanceof NestedRuntimeException) {
return getTargetStateResolver13((NestedRuntimeException)e.getCause());
}
else {
return null;
}
}
}
}
/**
* Internal getTargetStateResolver implementation for use with JDK 1.4 or later.
*/
private TargetStateResolver getTargetStateResolver14(Throwable t) {
TargetStateResolver targetStateResolver;
if (isRootCause14(t)) {
return findTargetStateResolver(t.getClass());
}
else {
targetStateResolver = (TargetStateResolver)exceptionTargetStateMappings.get(t.getClass());
if (targetStateResolver != null) {
return targetStateResolver;
}
else {
return getTargetStateResolver14(t.getCause());
}
}
}
/**
* Check if given exception is the root of the exception cause chain.
* For use with JDK 1.3.
*/
private boolean isRootCause13(NestedRuntimeException e) {
return e.getCause() == null;
}
/**
* Check if given exception is the root of the exception cause chain.
* For use with JDK 1.4 or later.
*/
private boolean isRootCause14(Throwable t) {
return t.getCause() == null;
}
/**
* Try to find a mapped target state resolver for given exception type. Will
* also try to lookup using the class hierarchy of given exception type.
* @param exceptionType the exception type to lookup
* @return the target state id or null if not found
*/
private TargetStateResolver findTargetStateResolver(Class exceptionType) {
while (exceptionType != null && exceptionType.getClass() != Object.class) {
if (exceptionTargetStateMappings.containsKey(exceptionType)) {
return (TargetStateResolver)exceptionTargetStateMappings.get(exceptionType);
}
else {
exceptionType = exceptionType.getSuperclass();
}
}
return null;
}
/**
* Find the root cause of given throwable.
*/
protected Throwable findRootCause(Throwable t) {
if (JdkVersion.getMajorJavaVersion() == JdkVersion.JAVA_13) {
return findRootCause13(t);
}
else {
return findRootCause14(t);
}
}
/**
* Find the root cause of given throwable. For use on JDK 1.3.
*/
private Throwable findRootCause13(Throwable t) {
if (t instanceof NestedRuntimeException) {
NestedRuntimeException nre = (NestedRuntimeException)t;
Throwable cause = nre.getCause();
if (cause == null) {
return nre;
}
else {
return findRootCause13(cause);
}
}
else {
return t;
}
}
/**
* Find the root cause of given throwable. For use on JDK 1.4 or later.
*/
private Throwable findRootCause14(Throwable e) {
Throwable cause = e.getCause();
if (cause == null) {
return e;
}
else {
return findRootCause14(cause);
}
}
public String toString() {
return new ToStringCreator(this).append("exceptionTargetStateMappings", exceptionTargetStateMappings)
.toString();
}
}