/* * Copyright 2002-2007,2009 The Apache Software Foundation. * * 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.opensymphony.xwork2.interceptor; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.interceptor.annotations.InputConfig; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.opensymphony.xwork2.util.AnnotationUtils; import java.lang.reflect.Method; /** * <!-- START SNIPPET: description --> * <p> * An interceptor that makes sure there are not validation, conversion or action errors before allowing the interceptor chain to continue. * If a single FieldError or ActionError (including the ones replicated by the Message Store Interceptor in a redirection) is found, the INPUT result will be triggered. * <b>This interceptor does not perform any validation</b>. * </p> * * <p> * This interceptor does nothing if the name of the method being invoked is specified in the <b>excludeMethods</b> * parameter. <b>excludeMethods</b> accepts a comma-delimited list of method names. For example, requests to * <b>foo!input.action</b> and <b>foo!back.action</b> will be skipped by this interceptor if you set the * <b>excludeMethods</b> parameter to "input, back". * </p> * * <p> * <b>Note:</b> As this method extends off MethodFilterInterceptor, it is capable of * deciding if it is applicable only to selective methods in the action class. This is done by adding param tags * for the interceptor element, naming either a list of excluded method names and/or a list of included method * names, whereby includeMethods overrides excludedMethods. A single * sign is interpreted as wildcard matching * all methods for both parameters. * See {@link MethodFilterInterceptor} for more info. * </p> * * <p> * This interceptor also supports the following interfaces which can implemented by actions: * </p> * * <ul> * <li>ValidationAware - implemented by ActionSupport class</li> * <li>ValidationWorkflowAware - allows changing result name programmatically</li> * <li>ValidationErrorAware - notifies action about errors and also allow change result name</li> * </ul> * * <p> * You can also use InputConfig annotation to change result name returned when validation errors occurred. * </p> * * <!-- END SNIPPET: description --> * * <p><u>Interceptor parameters:</u></p> * * <!-- START SNIPPET: parameters --> * <ul> * <li>inputResultName - Default to "input". Determine the result name to be returned when * an action / field error is found.</li> * </ul> * <!-- END SNIPPET: parameters --> * * <p><u>Extending the interceptor:</u></p> * * <!-- START SNIPPET: extending --> * * <p>There are no known extension points for this interceptor.</p> * * <!-- END SNIPPET: extending --> * * <p><u>Example code:</u></p> * * <pre> * <!-- START SNIPPET: example --> * * <action name="someAction" class="com.examples.SomeAction"> * <interceptor-ref name="params"/> * <interceptor-ref name="validation"/> * <interceptor-ref name="workflow"/> * <result name="success">good_result.ftl</result> * </action> * * <-- In this case myMethod as well as mySecondMethod of the action class * will not pass through the workflow process --> * <action name="someAction" class="com.examples.SomeAction"> * <interceptor-ref name="params"/> * <interceptor-ref name="validation"/> * <interceptor-ref name="workflow"> * <param name="excludeMethods">myMethod,mySecondMethod</param> * </interceptor-ref name="workflow"> * <result name="success">good_result.ftl</result> * </action> * * <-- In this case, the result named "error" will be used when * an action / field error is found --> * <-- The Interceptor will only be applied for myWorkflowMethod method of action * classes, since this is the only included method while any others are excluded --> * <action name="someAction" class="com.examples.SomeAction"> * <interceptor-ref name="params"/> * <interceptor-ref name="validation"/> * <interceptor-ref name="workflow"> * <param name="inputResultName">error</param> * <param name="excludeMethods">*</param> * <param name="includeMethods">myWorkflowMethod</param> * </interceptor-ref> * <result name="success">good_result.ftl</result> * </action> * * <!-- END SNIPPET: example --> * </pre> * * @author Jason Carreira * @author Rainer Hermanns * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a> * @author Philip Luppens * @author tm_jee */ public class DefaultWorkflowInterceptor extends MethodFilterInterceptor { private static final long serialVersionUID = 7563014655616490865L; private static final Logger LOG = LogManager.getLogger(DefaultWorkflowInterceptor.class); private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; private String inputResultName = Action.INPUT; /** * Set the <code>inputResultName</code> (result name to be returned when * a action / field error is found registered). Default to {@link Action#INPUT} * * @param inputResultName what result name to use when there was validation error(s). */ public void setInputResultName(String inputResultName) { this.inputResultName = inputResultName; } /** * Intercept {@link ActionInvocation} and returns a <code>inputResultName</code> * when action / field errors is found registered. * * @param invocation the action invocation * @return String result name */ @Override protected String doIntercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction(); if (action instanceof ValidationAware) { ValidationAware validationAwareAction = (ValidationAware) action; if (validationAwareAction.hasErrors()) { LOG.debug("Errors on action [{}], returning result name [{}]", validationAwareAction, inputResultName); String resultName = inputResultName; resultName = processValidationWorkflowAware(action, resultName); resultName = processInputConfig(action, invocation.getProxy().getMethod(), resultName); resultName = processValidationErrorAware(action, resultName); return resultName; } } return invocation.invoke(); } /** * Process {@link ValidationWorkflowAware} interface * * @param action action object * @param currentResultName current result name * * @return result name */ private String processValidationWorkflowAware(final Object action, final String currentResultName) { String resultName = currentResultName; if (action instanceof ValidationWorkflowAware) { resultName = ((ValidationWorkflowAware) action).getInputResultName(); LOG.debug("Changing result name from [{}] to [{}] because of processing [{}] interface applied to [{}]", currentResultName, resultName, ValidationWorkflowAware.class.getSimpleName(), action); } return resultName; } /** * Process {@link InputConfig} annotation applied to method * @param action action object * @param method method * @param currentResultName current result name * * @return result name * * @throws Exception in case of any errors */ protected String processInputConfig(final Object action, final String method, final String currentResultName) throws Exception { String resultName = currentResultName; InputConfig annotation = AnnotationUtils.findAnnotation(action.getClass().getMethod(method, EMPTY_CLASS_ARRAY), InputConfig.class); if (annotation != null) { if (StringUtils.isNotEmpty(annotation.methodName())) { resultName = (String) MethodUtils.invokeMethod(action, true, annotation.methodName()); } else { resultName = annotation.resultName(); } LOG.debug("Changing result name from [{}] to [{}] because of processing annotation [{}] on action [{}]", currentResultName, resultName, InputConfig.class.getSimpleName(), action); } return resultName; } /** * Notify action if it implements {@link ValidationErrorAware} interface * * @param action action object * @param currentResultName current result name * * @return result name * @see ValidationErrorAware */ protected String processValidationErrorAware(final Object action, final String currentResultName) { String resultName = currentResultName; if (action instanceof ValidationErrorAware) { resultName = ((ValidationErrorAware) action).actionErrorOccurred(currentResultName); LOG.debug("Changing result name from [{}] to [{}] because of processing interface [{}] on action [{}]", currentResultName, resultName, ValidationErrorAware.class.getSimpleName(), action); } return resultName; } }