001: /*
002: * $Id: ActionComponent.java 497654 2007-01-19 00:21:57Z rgielen $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.components;
022:
023: import java.io.IOException;
024: import java.io.Writer;
025: import java.util.HashMap;
026: import java.util.Map;
027:
028: import javax.servlet.ServletContext;
029: import javax.servlet.http.HttpServletRequest;
030: import javax.servlet.http.HttpServletResponse;
031: import javax.servlet.jsp.PageContext;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.apache.struts2.views.annotations.StrutsTag;
036: import org.apache.struts2.views.annotations.StrutsTagAttribute;
037: import org.apache.struts2.ServletActionContext;
038: import org.apache.struts2.StrutsException;
039: import org.apache.struts2.dispatcher.Dispatcher;
040: import org.apache.struts2.dispatcher.RequestMap;
041: import org.apache.struts2.views.jsp.TagUtils;
042:
043: import com.opensymphony.xwork2.ActionContext;
044: import com.opensymphony.xwork2.ActionProxy;
045: import com.opensymphony.xwork2.ActionProxyFactory;
046: import com.opensymphony.xwork2.inject.Inject;
047: import com.opensymphony.xwork2.util.ValueStack;
048: import com.opensymphony.xwork2.util.ValueStackFactory;
049:
050: /**
051: * <!-- START SNIPPET: javadoc -->
052: * <p>This tag enables developers to call actions directly from a JSP page by specifying the action name and an optional
053: * namespace. The body content of the tag is used to render the results from the Action. Any result processor defined
054: * for this action in struts.xml will be ignored, <i>unless</i> the executeResult parameter is specified.</p>
055: * <!-- END SNIPPET: javadoc -->
056: *
057: * <!-- START SNIPPET: params -->
058: * <ul>
059: * <li>id (String) - the id (if specified) to put the action under stack's context.
060: * <li>name* (String) - name of the action to be executed (without the extension suffix eg. .action)</li>
061: * <li>namespace (String) - default to the namespace where this action tag is invoked</li>
062: * <li>executeResult (Boolean) - default is false. Decides wheather the result of this action is to be executed or not</li>
063: * <li>ignoreContextParams (Boolean) - default to false. Decides wheather the request parameters are to be included when the action is invoked</li>
064: * </ul>
065: * <!-- END SNIPPET: params -->
066: *
067: * <pre>
068: * <!-- START SNIPPET: javacode -->
069: * public class ActionTagAction extends ActionSupport {
070: *
071: * public String execute() throws Exception {
072: * return "done";
073: * }
074: *
075: * public String doDefault() throws Exception {
076: * ServletActionContext.getRequest().setAttribute("stringByAction", "This is a String put in by the action's doDefault()");
077: * return "done";
078: * }
079: * }
080: * <!-- END SNIPPET: javacode -->
081: * </pre>
082: *
083: * <pre>
084: * <!-- START SNIPPET: strutsxml -->
085: * <xwork>
086: * ....
087: * <action name="actionTagAction1" class="tmjee.testing.ActionTagAction">
088: * <result name="done">success.jsp</result>
089: * </action>
090: * <action name="actionTagAction2" class="tmjee.testing.ActionTagAction" method="default">
091: * <result name="done">success.jsp</result>
092: * </action>
093: * ....
094: * </xwork>
095: * <!-- END SNIPPET: strutsxml -->
096: * </pre>
097: *
098: * <pre>
099: * <!-- START SNIPPET: example -->
100: * <div>The following action tag will execute result and include it in this page</div>
101: * <br />
102: * <s:action name="actionTagAction" executeResult="true" />
103: * <br />
104: * <div>The following action tag will do the same as above, but invokes method specialMethod in action</div>
105: * <br />
106: * <s:action name="actionTagAction!specialMethod" executeResult="true" />
107: * <br />
108: * <div>The following action tag will not execute result, but put a String in request scope
109: * under an id "stringByAction" which will be retrieved using property tag</div>
110: * <s:action name="actionTagAction!default" executeResult="false" />
111: * <s:property value="#attr.stringByAction" />
112: * <!-- END SNIPPET: example -->
113: * </pre>
114: *
115: */
116: @StrutsTag(name="action",tldTagClass="org.apache.struts2.views.jsp.ActionTag",description="Execute an action from within a view")
117: public class ActionComponent extends Component {
118: private static final Log LOG = LogFactory
119: .getLog(ActionComponent.class);
120:
121: protected HttpServletResponse res;
122: protected HttpServletRequest req;
123:
124: protected ActionProxyFactory actionProxyFactory;
125: protected ActionProxy proxy;
126: protected String name;
127: protected String namespace;
128: protected boolean executeResult;
129: protected boolean ignoreContextParams;
130: protected boolean flush = true;
131:
132: public ActionComponent(ValueStack stack, HttpServletRequest req,
133: HttpServletResponse res) {
134: super (stack);
135: this .req = req;
136: this .res = res;
137: }
138:
139: /**
140: * @param actionProxyFactory the actionProxyFactory to set
141: */
142: @Inject
143: public void setActionProxyFactory(
144: ActionProxyFactory actionProxyFactory) {
145: this .actionProxyFactory = actionProxyFactory;
146: }
147:
148: public boolean end(Writer writer, String body) {
149: boolean end = super .end(writer, "", false);
150: try {
151: if (flush) {
152: try {
153: writer.flush();
154: } catch (IOException e) {
155: LOG.warn("error while trying to flush writer ", e);
156: }
157: }
158: executeAction();
159:
160: if ((getId() != null) && (proxy != null)) {
161: getStack().setValue("#attr['" + getId() + "']",
162: proxy.getAction());
163: }
164: } finally {
165: popComponentStack();
166: }
167: return end;
168: }
169:
170: private Map createExtraContext() {
171: Map parentParams = null;
172:
173: if (!ignoreContextParams) {
174: parentParams = new ActionContext(getStack().getContext())
175: .getParameters();
176: }
177:
178: Map newParams = (parentParams != null) ? new HashMap(
179: parentParams) : new HashMap();
180:
181: if (parameters != null) {
182: newParams.putAll(parameters);
183: }
184:
185: ActionContext ctx = new ActionContext(stack.getContext());
186: ServletContext servletContext = (ServletContext) ctx
187: .get(ServletActionContext.SERVLET_CONTEXT);
188: PageContext pageContext = (PageContext) ctx
189: .get(ServletActionContext.PAGE_CONTEXT);
190: Map session = ctx.getSession();
191: Map application = ctx.getApplication();
192:
193: Dispatcher du = Dispatcher.getInstance();
194: Map extraContext = du.createContextMap(new RequestMap(req),
195: newParams, session, application, req, res,
196: servletContext);
197:
198: ValueStack newStack = ValueStackFactory.getFactory()
199: .createValueStack(stack);
200: extraContext.put(ActionContext.VALUE_STACK, newStack);
201:
202: // add page context, such that ServletDispatcherResult will do an include
203: extraContext
204: .put(ServletActionContext.PAGE_CONTEXT, pageContext);
205:
206: return extraContext;
207: }
208:
209: public ActionProxy getProxy() {
210: return proxy;
211: }
212:
213: /**
214: * Execute the requested action. If no namespace is provided, we'll
215: * attempt to derive a namespace using buildNamespace(). The ActionProxy
216: * and the namespace will be saved into the instance variables proxy and
217: * namespace respectively.
218: *
219: * @see org.apache.struts2.views.jsp.TagUtils#buildNamespace
220: */
221: private void executeAction() {
222: String actualName = findString(name, "name",
223: "Action name is required. Example: updatePerson");
224:
225: if (actualName == null) {
226: throw new StrutsException("Unable to find value for name "
227: + name);
228: }
229:
230: // handle "name!method" convention.
231: final String actionName;
232: final String methodName;
233:
234: int exclamation = actualName.lastIndexOf("!");
235: if (exclamation != -1) {
236: actionName = actualName.substring(0, exclamation);
237: methodName = actualName.substring(exclamation + 1);
238: } else {
239: actionName = actualName;
240: methodName = null;
241: }
242:
243: String namespace;
244:
245: if (this .namespace == null) {
246: namespace = TagUtils.buildNamespace(actionMapper,
247: getStack(), req);
248: } else {
249: namespace = findString(this .namespace);
250: }
251:
252: // get the old value stack from the request
253: ValueStack stack = getStack();
254: // execute at this point, after params have been set
255: try {
256:
257: proxy = actionProxyFactory.createActionProxy(namespace,
258: actionName, createExtraContext(), executeResult,
259: true);
260: if (null != methodName) {
261: proxy.setMethod(methodName);
262: }
263: // set the new stack into the request for the taglib to use
264: req.setAttribute(
265: ServletActionContext.STRUTS_VALUESTACK_KEY, proxy
266: .getInvocation().getStack());
267: proxy.execute();
268:
269: } catch (Exception e) {
270: String message = "Could not execute action: " + namespace
271: + "/" + actualName;
272: LOG.error(message, e);
273: } finally {
274: // set the old stack back on the request
275: req.setAttribute(
276: ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
277: }
278:
279: if ((getId() != null) && (proxy != null)) {
280: final Map context = stack.getContext();
281: context.put(getId(), proxy.getAction());
282: }
283: }
284:
285: @StrutsTagAttribute(required=true,description="Name of the action to be executed (without the extension suffix eg. .action)")
286: public void setName(String name) {
287: this .name = name;
288: }
289:
290: @StrutsTagAttribute(description="Namespace for action to call",defaultValue="namespace from where tag is used")
291: public void setNamespace(String namespace) {
292: this .namespace = namespace;
293: }
294:
295: @StrutsTagAttribute(description="Whether the result of this action (probably a view) should be executed/rendered",type="Boolean",defaultValue="false")
296: public void setExecuteResult(boolean executeResult) {
297: this .executeResult = executeResult;
298: }
299:
300: @StrutsTagAttribute(description="Whether the request parameters are to be included when the action is invoked",type="Boolean",defaultValue="false")
301: public void setIgnoreContextParams(boolean ignoreContextParams) {
302: this .ignoreContextParams = ignoreContextParams;
303: }
304:
305: @StrutsTagAttribute(description="Whether the writer should be flush upon end of action component tag, default to true",type="Boolean",defaultValue="true")
306: public void setFlush(boolean flush) {
307: this.flush = flush;
308: }
309: }
|