View Javadoc

1   /*
2    * $Id: ActionComponent.java 497654 2007-01-19 00:21:57Z rgielen $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  package org.apache.struts2.components;
22  
23  import java.io.IOException;
24  import java.io.Writer;
25  import java.util.HashMap;
26  import java.util.Map;
27  
28  import javax.servlet.ServletContext;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  import javax.servlet.jsp.PageContext;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.struts2.views.annotations.StrutsTag;
36  import org.apache.struts2.views.annotations.StrutsTagAttribute;
37  import org.apache.struts2.ServletActionContext;
38  import org.apache.struts2.StrutsException;
39  import org.apache.struts2.dispatcher.Dispatcher;
40  import org.apache.struts2.dispatcher.RequestMap;
41  import org.apache.struts2.views.jsp.TagUtils;
42  
43  import com.opensymphony.xwork2.ActionContext;
44  import com.opensymphony.xwork2.ActionProxy;
45  import com.opensymphony.xwork2.ActionProxyFactory;
46  import com.opensymphony.xwork2.inject.Inject;
47  import com.opensymphony.xwork2.util.ValueStack;
48  import com.opensymphony.xwork2.util.ValueStackFactory;
49  
50  /***
51   * <!-- START SNIPPET: javadoc -->
52   * <p>This tag enables developers to call actions directly from a JSP page by specifying the action name and an optional
53   * namespace.  The body content of the tag is used to render the results from the Action.  Any result processor defined
54   * for this action in struts.xml will be ignored, <i>unless</i> the executeResult parameter is specified.</p>
55   * <!-- END SNIPPET: javadoc -->
56   *
57   * <!-- START SNIPPET: params -->
58   * <ul>
59   *      <li>id (String) - the id (if specified) to put the action under stack's context.
60   *      <li>name* (String) - name of the action to be executed (without the extension suffix eg. .action)</li>
61   *      <li>namespace (String) - default to the namespace where this action tag is invoked</li>
62   *      <li>executeResult (Boolean) -  default is false. Decides wheather the result of this action is to be executed or not</li>
63   *      <li>ignoreContextParams (Boolean) - default to false. Decides wheather the request parameters are to be included when the action is invoked</li>
64   * </ul>
65   * <!-- END SNIPPET: params -->
66   *
67   * <pre>
68   * <!-- START SNIPPET: javacode -->
69   * public class ActionTagAction extends ActionSupport {
70   *
71   *  public String execute() throws Exception {
72   *      return "done";
73   *  }
74   *
75   *  public String doDefault() throws Exception {
76   *      ServletActionContext.getRequest().setAttribute("stringByAction", "This is a String put in by the action's doDefault()");
77   *      return "done";
78   *  }
79   * }
80   * <!-- END SNIPPET: javacode -->
81   * </pre>
82   *
83   * <pre>
84   * <!-- START SNIPPET: strutsxml -->
85   *   <xwork>
86   *      ....
87   *     <action name="actionTagAction1" class="tmjee.testing.ActionTagAction">
88   *         <result name="done">success.jsp</result>
89   *     </action>
90   *      <action name="actionTagAction2" class="tmjee.testing.ActionTagAction" method="default">
91   *         <result name="done">success.jsp</result>
92   *     </action>
93   *      ....
94   *   </xwork>
95   * <!-- END SNIPPET: strutsxml -->
96   * </pre>
97   *
98   * <pre>
99   * <!-- 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.getLog(ActionComponent.class);
119 
120     protected HttpServletResponse res;
121     protected HttpServletRequest req;
122 
123     protected ActionProxyFactory actionProxyFactory;
124     protected ActionProxy proxy;
125     protected String name;
126     protected String namespace;
127     protected boolean executeResult;
128     protected boolean ignoreContextParams;
129     protected boolean flush = true;
130 
131     public ActionComponent(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
132         super(stack);
133         this.req = req;
134         this.res = res;
135     }
136 
137     /***
138      * @param actionProxyFactory the actionProxyFactory to set
139      */
140     @Inject
141     public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
142         this.actionProxyFactory = actionProxyFactory;
143     }
144 
145     public boolean end(Writer writer, String body) {
146         boolean end = super.end(writer, "", false);
147         try {
148             if (flush) {
149                 try {
150                     writer.flush();
151                 } catch (IOException e) {
152                     LOG.warn("error while trying to flush writer ", e);
153                 }
154             }
155             executeAction();
156 
157             if ((getId() != null) && (proxy != null)) {
158                 getStack().setValue("#attr['" + getId() + "']",
159                         proxy.getAction());
160             }
161         } finally {
162             popComponentStack();
163         }
164         return end;
165     }
166 
167     private Map createExtraContext() {
168         Map parentParams = null;
169 
170         if (!ignoreContextParams) {
171             parentParams = new ActionContext(getStack().getContext()).getParameters();
172         }
173 
174         Map newParams = (parentParams != null) ? new HashMap(parentParams) : new HashMap();
175 
176         if (parameters != null) {
177             newParams.putAll(parameters);
178         }
179 
180         ActionContext ctx = new ActionContext(stack.getContext());
181         ServletContext servletContext = (ServletContext) ctx.get(ServletActionContext.SERVLET_CONTEXT);
182         PageContext pageContext = (PageContext) ctx.get(ServletActionContext.PAGE_CONTEXT);
183         Map session = ctx.getSession();
184         Map application = ctx.getApplication();
185 
186         Dispatcher du = Dispatcher.getInstance();
187         Map extraContext = du.createContextMap(new RequestMap(req),
188                 newParams,
189                 session,
190                 application,
191                 req,
192                 res,
193                 servletContext);
194 
195         ValueStack newStack = ValueStackFactory.getFactory().createValueStack(stack);
196         extraContext.put(ActionContext.VALUE_STACK, newStack);
197 
198         // add page context, such that ServletDispatcherResult will do an include
199         extraContext.put(ServletActionContext.PAGE_CONTEXT, pageContext);
200 
201         return extraContext;
202     }
203 
204     public ActionProxy getProxy() {
205         return proxy;
206     }
207 
208     /***
209      * Execute the requested action.  If no namespace is provided, we'll
210      * attempt to derive a namespace using buildNamespace().  The ActionProxy
211      * and the namespace will be saved into the instance variables proxy and
212      * namespace respectively.
213      *
214      * @see org.apache.struts2.views.jsp.TagUtils#buildNamespace
215      */
216     private void executeAction() {
217         String actualName = findString(name, "name", "Action name is required. Example: updatePerson");
218 
219         if (actualName == null) {
220             throw new StrutsException("Unable to find value for name " + name);
221         }
222 
223         // handle "name!method" convention.
224         final String actionName;
225         final String methodName;
226 
227         int exclamation = actualName.lastIndexOf("!");
228         if (exclamation != -1) {
229             actionName = actualName.substring(0, exclamation);
230             methodName = actualName.substring(exclamation + 1);
231         } else {
232             actionName = actualName;
233             methodName = null;
234         }
235 
236         String namespace;
237 
238         if (this.namespace == null) {
239             namespace = TagUtils.buildNamespace(actionMapper, getStack(), req);
240         } else {
241             namespace = findString(this.namespace);
242         }
243 
244         // get the old value stack from the request
245         ValueStack stack = getStack();
246         // execute at this point, after params have been set
247         try {
248 
249             proxy = actionProxyFactory.createActionProxy(namespace, actionName, createExtraContext(), executeResult, true);
250             if (null != methodName) {
251                 proxy.setMethod(methodName);
252             }
253             // set the new stack into the request for the taglib to use
254             req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
255             proxy.execute();
256 
257         } catch (Exception e) {
258             String message = "Could not execute action: " + namespace + "/" + actualName;
259             LOG.error(message, e);
260         } finally {
261             // set the old stack back on the request
262             req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
263         }
264 
265         if ((getId() != null) && (proxy != null)) {
266             final Map context = stack.getContext();
267             context.put(getId(), proxy.getAction());
268         }
269     }
270 
271     @StrutsTagAttribute(required=true,description="Name of the action to be executed (without the extension suffix eg. .action)")
272     public void setName(String name) {
273         this.name = name;
274     }
275 
276     @StrutsTagAttribute(description="Namespace for action to call", defaultValue="namespace from where tag is used")
277     public void setNamespace(String namespace) {
278         this.namespace = namespace;
279     }
280 
281     @StrutsTagAttribute(description="Whether the result of this action (probably a view) should be executed/rendered", type="Boolean", defaultValue="false")
282     public void setExecuteResult(boolean executeResult) {
283         this.executeResult = executeResult;
284     }
285 
286     @StrutsTagAttribute(description="Whether the request parameters are to be included when the action is invoked", type="Boolean", defaultValue="false")
287     public void setIgnoreContextParams(boolean ignoreContextParams) {
288         this.ignoreContextParams = ignoreContextParams;
289     }
290 
291     @StrutsTagAttribute(description="Whether the writer should be flush upon end of action component tag, default to true", type="Boolean", defaultValue="true")
292     public void setFlush(boolean flush) {
293         this.flush = flush;
294     }
295 }