blob: 7cc3f4343cb62055c35e5b6c2f8addb511f81e7f [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 org.apache.struts2.components;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.ActionProxyFactory;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.ValueStackFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsException;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import org.apache.struts2.views.jsp.TagUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
/**
* <!-- START SNIPPET: javadoc -->
* <p>This tag enables developers to call actions directly from a JSP page by specifying the action name and an optional
* namespace. The body content of the tag is used to render the results from the Action. Any result processor defined
* for this action in struts.xml will be ignored, <i>unless</i> the executeResult parameter is specified.</p>
* <!-- END SNIPPET: javadoc -->
*
* <!-- START SNIPPET: params -->
* <ul>
* <li>id (String) - the id (if specified) to put the action under stack's context.
* <li>name* (String) - name of the action to be executed (without the extension suffix eg. .action)</li>
* <li>namespace (String) - default to the namespace where this action tag is invoked</li>
* <li>executeResult (Boolean) - default is false. Decides whether the result of this action is to be executed or not</li>
* <li>ignoreContextParams (Boolean) - default to false. Decides whether the request parameters are to be included when the action is invoked</li>
* </ul>
* <!-- END SNIPPET: params -->
*
* <pre>
* <!-- START SNIPPET: javacode -->
* public class ActionTagAction extends ActionSupport {
*
* public String execute() throws Exception {
* return "done";
* }
*
* public String doDefault() throws Exception {
* ServletActionContext.getRequest().setAttribute("stringByAction", "This is a String put in by the action's doDefault()");
* return "done";
* }
* }
* <!-- END SNIPPET: javacode -->
* </pre>
*
* <pre>
* <!-- START SNIPPET: strutsxml -->
* &lt;xwork&gt;
* ....
* &lt;action name=&quot;actionTagAction1&quot; class=&quot;tmjee.testing.ActionTagAction&quot;&gt;
* &lt;result name=&quot;done&quot;&gt;success.jsp&lt;/result&gt;
* &lt;/action&gt;
* &lt;action name=&quot;actionTagAction2&quot; class=&quot;tmjee.testing.ActionTagAction&quot; method=&quot;default&quot;&gt;
* &lt;result name=&quot;done&quot;&gt;success.jsp&lt;/result&gt;
* &lt;/action&gt;
* ....
* &lt;/xwork&gt;
* <!-- END SNIPPET: strutsxml -->
* </pre>
*
* <pre>
* <!-- START SNIPPET: example -->
* <span>The following action tag will execute result and include it in this page</span>
* <br>
* &lt;s:action name=&quot;actionTagAction&quot; executeResult=&quot;true&quot; /&gt;
* <br>
* <span>The following action tag will do the same as above, but invokes method specialMethod in action</span>
* <br>
* &lt;s:action name=&quot;actionTagAction!specialMethod&quot; executeResult=&quot;true&quot; /&gt;
* <br>
* <span>The following action tag will not execute result, but put a String in request scope
* under an id "stringByAction" which will be retrieved using property tag</span>
* &lt;s:action name=&quot;actionTagAction!default&quot; executeResult=&quot;false&quot; /&gt;
* &lt;s:property value=&quot;#attr.stringByAction&quot; /&gt;
* <!-- END SNIPPET: example -->
* </pre>
*
*/
@StrutsTag(name="action", tldTagClass="org.apache.struts2.views.jsp.ActionTag", description="Execute an action from within a view")
public class ActionComponent extends ContextBean {
private static final Logger LOG = LogManager.getLogger(ActionComponent.class);
protected HttpServletResponse res;
protected HttpServletRequest req;
protected ValueStackFactory valueStackFactory;
protected ActionProxyFactory actionProxyFactory;
protected ActionProxy proxy;
protected String name;
protected String namespace;
protected boolean executeResult;
protected boolean ignoreContextParams;
protected boolean flush = true;
protected boolean rethrowException;
public ActionComponent(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
super(stack);
this.req = req;
this.res = res;
}
/**
* @param actionProxyFactory the actionProxyFactory to set
*/
@Inject
public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
this.actionProxyFactory = actionProxyFactory;
}
@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
}
@Inject
@Override
public void setActionMapper(ActionMapper mapper) {
this.actionMapper = mapper;
}
@Override
public boolean end(Writer writer, String body) {
boolean end = super.end(writer, "", false);
try {
if (flush) {
try {
writer.flush();
} catch (IOException e) {
LOG.warn("error while trying to flush writer ", e);
}
}
executeAction();
if ((getVar() != null) && (proxy != null)) {
getStack().setValue("#attr['" + getVar() + "']", proxy.getAction());
}
} finally {
popComponentStack();
}
return end;
}
protected Map<String, Object> createExtraContext() {
HttpParameters newParams = createParametersForContext();
ActionContext ctx = stack.getActionContext();
PageContext pageContext = ctx.getPageContext();
Map<String, Object> session = ctx.getSession();
Map<String, Object> application = ctx.getApplication();
Dispatcher du = Dispatcher.getInstance();
Map<String, Object> extraContext = du.createContextMap(
new RequestMap(req),
newParams,
session,
application,
req,
res);
ValueStack newStack = valueStackFactory.createValueStack(stack);
return ActionContext.of(extraContext)
.withValueStack(newStack)
// add page context, such that ServletDispatcherResult will do an include
.withPageContext(pageContext)
.getContextMap();
}
/**
* Creates parameters map using parameters from the value stack and component parameters. Any non-String array
* values will be converted into a single-value String array.
*
* @return A map of String[] parameters
*/
protected HttpParameters createParametersForContext() {
HttpParameters parentParams = null;
if (!ignoreContextParams) {
parentParams = getStack().getActionContext().getParameters();
}
HttpParameters.Builder builder = HttpParameters.create().withParent(parentParams);
if (parameters != null) {
builder = builder.withExtraParams(parameters);
}
return builder.build();
}
public ActionProxy getProxy() {
return proxy;
}
/**
* Execute the requested action. If no namespace is provided, we'll
* attempt to derive a namespace using buildNamespace(). The ActionProxy
* and the namespace will be saved into the instance variables proxy and
* namespace respectively.
*
* @see org.apache.struts2.views.jsp.TagUtils#buildNamespace
*/
protected void executeAction() {
String actualName = findString(name, "name", "Action name is required. Example: updatePerson");
if (actualName == null) {
throw new StrutsException("Unable to find value for name " + name);
}
// handle "name!method" convention.
final String actionName;
final String methodName;
ActionMapping mapping = actionMapper.getMappingFromActionName(actualName);
actionName = mapping.getName();
methodName = mapping.getMethod();
String namespace;
if (this.namespace == null) {
namespace = TagUtils.buildNamespace(actionMapper, getStack(), req);
} else {
namespace = findString(this.namespace);
}
// get the old value stack from the request
ValueStack stack = getStack();
// execute at this point, after params have been set
ActionInvocation inv = ActionContext.getContext().getActionInvocation();
try {
proxy = actionProxyFactory.createActionProxy(namespace, actionName, methodName, createExtraContext(), executeResult, true);
// set the new stack into the request for the taglib to use
req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
req.setAttribute(StrutsStatics.STRUTS_ACTION_TAG_INVOCATION, Boolean.TRUE);
proxy.execute();
} catch (Exception e) {
String message = "Could not execute action: " + namespace + "/" + actualName;
LOG.error(message, e);
if (rethrowException) {
throw new StrutsException(message, e);
}
} finally {
req.removeAttribute(StrutsStatics.STRUTS_ACTION_TAG_INVOCATION);
// set the old stack back on the request
req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
if (inv != null) {
ActionContext.getContext().withActionInvocation(inv);
}
}
if ((getVar() != null) && (proxy != null)) {
putInContext(proxy.getAction());
}
}
@StrutsTagAttribute(required=true,description="Name of the action to be executed (without the extension suffix eg. .action)")
public void setName(String name) {
this.name = name;
}
@StrutsTagAttribute(description="Namespace for action to call", defaultValue="namespace from where tag is used")
public void setNamespace(String namespace) {
this.namespace = namespace;
}
@StrutsTagAttribute(description="Whether the result of this action (probably a view) should be executed/rendered", type="Boolean", defaultValue="false")
public void setExecuteResult(boolean executeResult) {
this.executeResult = executeResult;
}
@StrutsTagAttribute(description="Whether the request parameters are to be included when the action is invoked", type="Boolean", defaultValue="false")
public void setIgnoreContextParams(boolean ignoreContextParams) {
this.ignoreContextParams = ignoreContextParams;
}
@StrutsTagAttribute(description="Whether the writer should be flush upon end of action component tag, default to true", type="Boolean", defaultValue="true")
public void setFlush(boolean flush) {
this.flush = flush;
}
@StrutsTagAttribute(description="Whether an exception should be rethrown, if the target action throws an exception", type="Boolean", defaultValue="false")
public void setRethrowException(boolean rethrowException) {
this.rethrowException = rethrowException;
}
}