blob: ea2076d4a17e39e979527243ed9ddaaf93eb6189 [file] [log] [blame]
/*
* 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;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.interceptor.WithLazyParams;
import com.opensymphony.xwork2.ognl.OgnlUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import ognl.MethodFailedException;
import ognl.NoSuchPropertyException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
/**
* The Default ActionInvocation implementation
*
* @author Rainer Hermanns
* @author tmjee
* @version $Date$ $Id$
* @see com.opensymphony.xwork2.DefaultActionProxy
*/
public class DefaultActionInvocation implements ActionInvocation {
private static final Logger LOG = LogManager.getLogger(DefaultActionInvocation.class);
protected Object action;
protected ActionProxy proxy;
protected List<PreResultListener> preResultListeners;
protected Map<String, Object> extraContext;
protected ActionContext invocationContext;
protected Iterator<InterceptorMapping> interceptors;
protected ValueStack stack;
protected Result result;
protected Result explicitResult;
protected String resultCode;
protected boolean executed = false;
protected boolean pushAction;
protected ObjectFactory objectFactory;
protected ActionEventListener actionEventListener;
protected ValueStackFactory valueStackFactory;
protected Container container;
protected UnknownHandlerManager unknownHandlerManager;
protected OgnlUtil ognlUtil;
protected AsyncManager asyncManager;
protected Callable asyncAction;
protected WithLazyParams.LazyParamInjector lazyParamInjector;
public DefaultActionInvocation(final Map<String, Object> extraContext, final boolean pushAction) {
this.extraContext = extraContext;
this.pushAction = pushAction;
}
@Inject
public void setUnknownHandlerManager(UnknownHandlerManager unknownHandlerManager) {
this.unknownHandlerManager = unknownHandlerManager;
}
@Inject
public void setValueStackFactory(ValueStackFactory fac) {
this.valueStackFactory = fac;
}
@Inject
public void setObjectFactory(ObjectFactory fac) {
this.objectFactory = fac;
}
@Inject
public void setContainer(Container cont) {
this.container = cont;
}
@Inject(required=false)
public void setActionEventListener(ActionEventListener listener) {
this.actionEventListener = listener;
}
@Inject
public void setOgnlUtil(OgnlUtil ognlUtil) {
this.ognlUtil = ognlUtil;
}
@Inject(required=false)
public void setAsyncManager(AsyncManager asyncManager) {
this.asyncManager = asyncManager;
}
public Object getAction() {
return action;
}
public boolean isExecuted() {
return executed;
}
public ActionContext getInvocationContext() {
return invocationContext;
}
public ActionProxy getProxy() {
return proxy;
}
/**
* If the DefaultActionInvocation has been executed before and the Result is an instance of ActionChainResult, this method
* will walk down the chain of ActionChainResults until it finds a non-chain result, which will be returned. If the
* DefaultActionInvocation's result has not been executed before, the Result instance will be created and populated with
* the result params.
*
* @return a Result instance
* @throws Exception in case of any error
*/
public Result getResult() throws Exception {
Result returnResult = result;
// If we've chained to other Actions, we need to find the last result
while (returnResult instanceof ActionChainResult) {
ActionProxy aProxy = ((ActionChainResult) returnResult).getProxy();
if (aProxy != null) {
Result proxyResult = aProxy.getInvocation().getResult();
if ((proxyResult != null) && (aProxy.getExecuteResult())) {
returnResult = proxyResult;
} else {
break;
}
} else {
break;
}
}
return returnResult;
}
public String getResultCode() {
return resultCode;
}
public void setResultCode(String resultCode) {
if (isExecuted()) {
throw new IllegalStateException("Result has already been executed.");
}
this.resultCode = resultCode;
}
public ValueStack getStack() {
return stack;
}
/**
* Register a com.opensymphony.xwork2.interceptor.PreResultListener to be notified after the Action is executed and before the
* Result is executed. The ActionInvocation implementation must guarantee that listeners will be called in the order
* in which they are registered. Listener registration and execution does not need to be thread-safe.
*
* @param listener to register
*/
public void addPreResultListener(PreResultListener listener) {
if (preResultListeners == null) {
preResultListeners = new ArrayList<>(1);
}
preResultListeners.add(listener);
}
public Result createResult() throws Exception {
LOG.trace("Creating result related to resultCode [{}]", resultCode);
if (explicitResult != null) {
Result ret = explicitResult;
explicitResult = null;
return ret;
}
ActionConfig config = proxy.getConfig();
Map<String, ResultConfig> results = config.getResults();
ResultConfig resultConfig = null;
try {
resultConfig = results.get(resultCode);
} catch (NullPointerException e) {
LOG.debug("Got NPE trying to read result configuration for resultCode [{}]", resultCode);
}
if (resultConfig == null) {
// If no result is found for the given resultCode, try to get a wildcard '*' match.
resultConfig = results.get("*");
}
if (resultConfig != null) {
try {
return objectFactory.buildResult(resultConfig, invocationContext.getContextMap());
} catch (Exception e) {
LOG.error("There was an exception while instantiating the result of type {}", resultConfig.getClassName(), e);
throw new StrutsException(e, resultConfig);
}
} else if (resultCode != null && !Action.NONE.equals(resultCode) && unknownHandlerManager.hasUnknownHandlers()) {
return unknownHandlerManager.handleUnknownResult(invocationContext, proxy.getActionName(), proxy.getConfig(), resultCode);
}
return null;
}
/**
* @throws ConfigurationException If no result can be found with the returned code
*/
public String invoke() throws Exception {
if (executed) {
throw new IllegalStateException("Action has already executed");
}
if (asyncManager == null || !asyncManager.hasAsyncActionResult()) {
if (interceptors.hasNext()) {
final InterceptorMapping interceptorMapping = interceptors.next();
Interceptor interceptor = interceptorMapping.getInterceptor();
if (interceptor instanceof WithLazyParams) {
interceptor = lazyParamInjector.injectParams(interceptor, interceptorMapping.getParams(), invocationContext);
}
resultCode = interceptor.intercept(DefaultActionInvocation.this);
} else {
resultCode = invokeActionOnly();
}
} else {
Object asyncActionResult = asyncManager.getAsyncActionResult();
if (asyncActionResult instanceof Throwable) {
throw new Exception((Throwable) asyncActionResult);
}
asyncAction = null;
resultCode = saveResult(proxy.getConfig(), asyncActionResult);
}
if (asyncManager == null || asyncAction == null) {
// this is needed because the result will be executed, then control will return to the Interceptor, which will
// return above and flow through again
if (!executed) {
if (preResultListeners != null) {
LOG.trace("Executing PreResultListeners for result [{}]", result);
for (Object preResultListener : preResultListeners) {
PreResultListener listener = (PreResultListener) preResultListener;
listener.beforeResult(this, resultCode);
}
}
// now execute the result, if we're supposed to
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
} else {
asyncManager.invokeAsyncAction(asyncAction);
}
return resultCode;
}
public String invokeActionOnly() throws Exception {
return invokeAction(getAction(), proxy.getConfig());
}
protected void createAction(Map<String, Object> contextMap) {
// load action
try {
action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);
} catch (InstantiationException e) {
throw new StrutsException("Unable to instantiate Action!", e, proxy.getConfig());
} catch (IllegalAccessException e) {
throw new StrutsException("Illegal access to constructor, is it public?", e, proxy.getConfig());
} catch (Exception e) {
String gripe;
if (proxy == null) {
gripe = "Whoa! No ActionProxy instance found in current ActionInvocation. This is bad ... very bad";
} else if (proxy.getConfig() == null) {
gripe = "Sheesh. Where'd that ActionProxy get to? I can't find it in the current ActionInvocation!?";
} else if (proxy.getConfig().getClassName() == null) {
gripe = "No Action defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
} else {
gripe = "Unable to instantiate Action, " + proxy.getConfig().getClassName() + ", defined for '" + proxy.getActionName() + "' in namespace '" + proxy.getNamespace() + "'";
}
gripe += (((" -- " + e.getMessage()) != null) ? e.getMessage() : " [no message in exception]");
throw new StrutsException(gripe, e, proxy.getConfig());
}
if (actionEventListener != null) {
action = actionEventListener.prepare(action, stack);
}
}
protected Map<String, Object> createContextMap() {
ActionContext actionContext;
if (ActionContext.containsValueStack(extraContext)) {
// In case the ValueStack was passed in
stack = ActionContext.of(extraContext).getValueStack();
if (stack == null) {
throw new IllegalStateException("There was a null Stack set into the extra params.");
}
actionContext = stack.getActionContext();
} else {
// create the value stack
// this also adds the ValueStack to its context
stack = valueStackFactory.createValueStack();
// create the action context
actionContext = stack.getActionContext();
}
return actionContext
.withExtraContext(extraContext)
.withActionInvocation(this)
.withContainer(container)
.getContextMap();
}
/**
* Uses getResult to get the final Result and executes it
*
* @throws ConfigurationException If not result can be found with the returned code
*/
private void executeResult() throws Exception {
result = createResult();
if (result != null) {
result.execute(this);
} else if (resultCode != null && !Action.NONE.equals(resultCode)) {
throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
+ " and result " + getResultCode(), proxy.getConfig());
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("No result returned for action {} at {}", getAction().getClass().getName(), proxy.getConfig().getLocation());
}
}
}
public void init(ActionProxy proxy) {
this.proxy = proxy;
Map<String, Object> contextMap = createContextMap();
// Setting this so that other classes, like object factories, can use the ActionProxy and other
// contextual information to operate
ActionContext actionContext = ActionContext.getContext();
if (actionContext != null) {
actionContext.withActionInvocation(this);
}
createAction(contextMap);
if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}
invocationContext = ActionContext.of(contextMap)
.withActionName(proxy.getActionName());
createInterceptors(proxy);
prepareLazyParamInjector(invocationContext.getValueStack());
}
protected void prepareLazyParamInjector(ValueStack valueStack) {
lazyParamInjector = new WithLazyParams.LazyParamInjector(valueStack);
container.inject(lazyParamInjector);
}
protected void createInterceptors(ActionProxy proxy) {
// Get a new List so we don't get problems with the iterator if someone changes the original list
List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
}
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
String methodName = proxy.getMethod();
LOG.debug("Executing action method = {}", methodName);
try {
Object methodResult;
try {
methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
} catch (MethodFailedException e) {
// if reason is missing method, try checking UnknownHandlers
if (e.getReason() instanceof NoSuchMethodException) {
if (unknownHandlerManager.hasUnknownHandlers()) {
try {
methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
} catch (NoSuchMethodException ignore) {
// throw the original one
throw e;
}
} else {
// throw the original one
throw e;
}
// throw the original exception as UnknownHandlers weren't able to handle invocation as well
if (methodResult == null) {
throw e;
}
} else {
// exception isn't related to missing action method, throw it
throw e;
}
}
return saveResult(actionConfig, methodResult);
} catch (NoSuchPropertyException e) {
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
} catch (MethodFailedException e) {
// We try to return the source exception.
Throwable t = e.getCause();
if (actionEventListener != null) {
String result = actionEventListener.handleException(t, getStack());
if (result != null) {
return result;
}
}
if (t instanceof Exception) {
throw (Exception) t;
} else {
throw e;
}
}
}
/**
* Save the result to be used later.
* @param actionConfig current ActionConfig
* @param methodResult the result of the action.
* @return the result code to process.
*/
protected String saveResult(ActionConfig actionConfig, Object methodResult) {
if (methodResult instanceof Result) {
this.explicitResult = (Result) methodResult;
// Wire the result automatically
container.inject(explicitResult);
return null;
} else if (methodResult instanceof Callable) {
asyncAction = (Callable) methodResult;
return null;
} else {
return (String) methodResult;
}
}
}