com.amashchenko.struts2.actionflow.ActionFlowInterceptor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of struts2-actionflow-plugin Show documentation
Show all versions of struts2-actionflow-plugin Show documentation
Struts2 ActionFlow Plugin. A Struts2 plugin for creating wizards (action flows).
/*
* Copyright 2013 Aleksandr Mashchenko.
*
* 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.amashchenko.struts2.actionflow;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.amashchenko.struts2.actionflow.entities.ActionFlowStepConfig;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
/**
*
*
* An interceptor that controls flow actions.
*
*
*
*
* Interceptor parameters:
*
*
*
*
*
* - nextActionName (optional) - Name of the 'next' action. If none is
* specified the default name
next
will be used.
*
* - prevActionName (optional) - Name of the 'previous' action. If none is
* specified the default name
prev
will be used.
*
* - forceFlowStepsOrder (optional) - To force the order of flow action
* executions. The default is
true
.
*
* - viewActionPostfix (optional) - String to append to generated view action
* name. The default is
View
.
*
* - viewActionMethod (optional) - Action method to execute in generated view
* actions. The default is
execute
.
*
* - stepParameterName (optional) - Name of the form parameter holding
* previous action value. The default is
step
.
*
*
*
*
*
*
*
*
* Extending the interceptor:
*
*
*
*
* There are no known extensions points for this interceptor.
*
*
*
* Example code:
*
*
*
* <action name="saveName" method="saveName" class="com.example.FlowAction">
* <param name="actionFlowStep">1</param>
*
* <result name="input">input_result.jsp</result>
* <result name="success">success_result.jsp</result>
* </action>
*
*
*
* You must use {@link #nextActionName} and {@link #prevActionName} in the form.
*
*
*
*
* <s:form action="next">
* <s:hidden name="step" value="%{#session['actionFlowPreviousAction']}" />
*
* <s:textfield name="name" label="Name" />
* <s:submit value="previous" action="prev" />
* <s:submit value="next" action="next" />
* </s:form>
*
*
*
*
* @author Aleksandr Mashchenko
*
*/
public class ActionFlowInterceptor extends AbstractInterceptor {
/** Serial version uid. */
private static final long serialVersionUID = -7520779074852595667L;
/** Logger. */
public static final Logger LOG = LoggerFactory
.getLogger(ActionFlowInterceptor.class);
private static final String PREVIOUS_FLOW_ACTION = "actionFlowPreviousAction";
private static final String DEFAULT_NEXT_ACTION_NAME = "next";
private static final String DEFAULT_PREV_ACTION_NAME = "prev";
protected static final String FIRST_FLOW_ACTION_NAME = "firstFlowAction";
protected static final String GLOBAL_VIEW_RESULT = "actionFlowViewResult";
private static final String DEFAULT_VIEW_ACTION_POSTFIX = "View";
private static final String DEFAULT_VIEW_ACTION_METHOD = "execute";
private static final String DEFAULT_STEP_PARAM_NAME = "step";
protected static final String NEXT_ACTION_PARAM = "nextAction";
protected static final String PREV_ACTION_PARAM = "prevAction";
protected static final String VIEW_ACTION_PARAM = "viewAction";
private static final String FLOW_SCOPE_KEY = "actionFlowScope";
private Map> flowScopeFields;
/** Previous not special nor flow action. */
private String prevSimpleAction;
/** Action before first next. */
private String startAction;
// interceptor parameters
private String nextActionName = DEFAULT_NEXT_ACTION_NAME;
private String prevActionName = DEFAULT_PREV_ACTION_NAME;
private boolean forceFlowStepsOrder = true;
private String viewActionPostfix = DEFAULT_VIEW_ACTION_POSTFIX;
private String viewActionMethod = DEFAULT_VIEW_ACTION_METHOD;
private String stepParameterName = DEFAULT_STEP_PARAM_NAME;
/** Holds action flow. */
private Map flowMap;
/** Action flow configuration builder. */
@Inject
private ActionFlowConfigBuilder flowConfigBuilder;
/** {@inheritDoc} */
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String actionName = invocation.getInvocationContext().getName();
if (flowMap == null) {
String packageName = invocation.getProxy().getConfig()
.getPackageName();
flowMap = flowConfigBuilder.createFlowMap(packageName,
nextActionName, prevActionName, viewActionPostfix,
viewActionMethod);
flowScopeFields = flowConfigBuilder
.createFlowScopeFields(packageName);
}
boolean flowAction = false;
boolean lastFlowAction = false;
if (flowMap.containsKey(actionName)) {
flowAction = true;
if (flowMap.get(actionName).getNextAction() == null) {
lastFlowAction = true;
}
}
boolean flowViewAction = false;
if (actionName.endsWith(viewActionPostfix)) {
String plainAction = actionName.substring(0,
actionName.indexOf(viewActionPostfix));
if (flowMap.containsKey(plainAction)) {
flowViewAction = true;
}
}
Map session = invocation.getInvocationContext()
.getSession();
// start
if (startAction != null && startAction.equals(actionName)) {
session.put(PREVIOUS_FLOW_ACTION, null);
session.put(FLOW_SCOPE_KEY, null);
}
// scope
if (flowViewAction) {
handleFlowScope(invocation.getAction(), session, true);
}
// not a flow nor next nor previous action, just invoke
if (!flowAction && !prevActionName.equals(actionName)
&& !nextActionName.equals(actionName)) {
prevSimpleAction = actionName;
return invocation.invoke();
}
String previousFlowAction = (String) session.get(PREVIOUS_FLOW_ACTION);
if (previousFlowAction == null) {
previousFlowAction = FIRST_FLOW_ACTION_NAME;
}
// handling of back/forward buttons
Object[] stepParam = (Object[]) invocation.getInvocationContext()
.getParameters().get(stepParameterName);
if (stepParam != null && stepParam.length > 0) {
String step = "" + stepParam[0];
if (step.isEmpty()) {
step = FIRST_FLOW_ACTION_NAME;
}
if (step != null && !step.equals(previousFlowAction)) {
// check indexes, step parameter action flow index cannot be
// greater than previousFlowAction index
if (flowMap.containsKey(previousFlowAction)
&& flowMap.containsKey(step)) {
int indexPrev = flowMap.get(previousFlowAction).getIndex();
int indexStep = flowMap.get(step).getIndex();
if (indexStep < indexPrev) {
if (LOG.isDebugEnabled()) {
LOG.debug("The 'previousFlowAction' value from session is '"
+ previousFlowAction
+ "', but '"
+ stepParameterName
+ "' parameter value is '"
+ step
+ "' The '"
+ stepParameterName
+ "' parameter value will be used for 'previousFlowAction'.");
}
previousFlowAction = step;
}
}
}
}
String nextAction = null;
String prevAction = null;
if (flowMap.containsKey(previousFlowAction)) {
nextAction = flowMap.get(previousFlowAction).getNextAction();
prevAction = flowMap.get(previousFlowAction).getPrevAction();
}
if (LOG.isDebugEnabled()) {
LOG.debug(actionName + "-> previousFlowAction: "
+ previousFlowAction + ", nextAction: " + nextAction
+ ", prevAction: " + prevAction);
}
// force order of flow actions
if (forceFlowStepsOrder && flowAction && !actionName.equals(nextAction)) {
if (LOG.isDebugEnabled()) {
LOG.debug("The forceFlowStepsOrder parameter is set to true. The '"
+ actionName
+ "' will not be executed because it is executed in the wrong order.");
}
invocation.getInvocationContext().getValueStack()
.set(VIEW_ACTION_PARAM, nextAction + viewActionPostfix);
return GLOBAL_VIEW_RESULT;
}
if (nextActionName.equals(actionName)) {
// set start action
if (startAction == null) {
startAction = prevSimpleAction;
}
invocation.getInvocationContext().getValueStack()
.set(NEXT_ACTION_PARAM, nextAction);
} else if (prevActionName.equals(actionName)) {
if (FIRST_FLOW_ACTION_NAME.equals(previousFlowAction)) {
invocation.getInvocationContext().getValueStack()
.set(PREV_ACTION_PARAM, nextAction + viewActionPostfix);
} else {
invocation
.getInvocationContext()
.getValueStack()
.set(PREV_ACTION_PARAM,
previousFlowAction + viewActionPostfix);
}
session.put(PREVIOUS_FLOW_ACTION, prevAction);
}
// execute global view result on not last flow action
if (flowAction && nextAction.equals(actionName) && !lastFlowAction) {
final String nextAct = flowMap.get(actionName).getNextAction();
invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation,
String resultCode) {
if (Action.SUCCESS.equals(resultCode)) {
invocation
.getInvocationContext()
.getValueStack()
.set(VIEW_ACTION_PARAM,
nextAct + viewActionPostfix);
invocation.setResultCode(GLOBAL_VIEW_RESULT);
}
}
});
}
String result = invocation.invoke();
// scope
if (flowAction) {
handleFlowScope(invocation.getAction(), session, false);
}
if (GLOBAL_VIEW_RESULT.equals(result) && flowAction) {
session.put(PREVIOUS_FLOW_ACTION, actionName);
}
// last flow action
if (Action.SUCCESS.equals(result) && flowAction && lastFlowAction) {
session.put(PREVIOUS_FLOW_ACTION, null);
session.put(FLOW_SCOPE_KEY, null);
}
return result;
}
/**
* Handles action flow scope fields.
*
* @param action
* action object.
* @param session
* session map.
* @param fromFlowScope
* whether to store value into the session or retrieve it.
*/
@SuppressWarnings("unchecked")
private void handleFlowScope(final Object action,
final Map session, final boolean fromFlowScope) {
if (action != null && flowScopeFields != null && session != null) {
String actionClassName = action.getClass().getName();
Map scopeMap = null;
if (session.containsKey(FLOW_SCOPE_KEY)
&& session.get(FLOW_SCOPE_KEY) instanceof Map) {
scopeMap = (Map) session.get(FLOW_SCOPE_KEY);
}
if (scopeMap == null) {
scopeMap = new HashMap();
}
if (flowScopeFields.containsKey(actionClassName)
&& flowScopeFields.get(actionClassName) != null) {
for (PropertyDescriptor pd : flowScopeFields
.get(actionClassName)) {
try {
Method getter = pd.getReadMethod();
if (getter != null) {
Object val = getter.invoke(action);
String scopeFieldKey = actionClassName + "."
+ pd.getName();
if (fromFlowScope) {
if (val == null
&& scopeMap.containsKey(scopeFieldKey)) {
Method setter = pd.getWriteMethod();
if (setter != null) {
setter.invoke(action,
scopeMap.get(scopeFieldKey));
}
}
} else {
if (val != null) {
scopeMap.put(scopeFieldKey, val);
session.put(FLOW_SCOPE_KEY, scopeMap);
}
}
}
} catch (Exception e) {
LOG.warn("In handleFlowScope", e);
}
}
}
}
}
/**
* @param nextActionName
* the nextActionName to set
*/
public void setNextActionName(String nextActionName) {
this.nextActionName = nextActionName;
}
/**
* @param prevActionName
* the prevActionName to set
*/
public void setPrevActionName(String prevActionName) {
this.prevActionName = prevActionName;
}
/**
* @param value
* the forceFlowStepsOrder to set
*/
public void setForceFlowStepsOrder(String value) {
this.forceFlowStepsOrder = Boolean.valueOf(value).booleanValue();
}
/**
* @param viewActionPostfix
* the viewActionPostfix to set
*/
public void setViewActionPostfix(String viewActionPostfix) {
this.viewActionPostfix = viewActionPostfix;
}
/**
* @param viewActionMethod
* the viewActionMethod to set
*/
public void setViewActionMethod(String viewActionMethod) {
this.viewActionMethod = viewActionMethod;
}
/**
* @param stepParameterName
* the stepParameterName to set
*/
public void setStepParameterName(String stepParameterName) {
this.stepParameterName = stepParameterName;
}
}