View Javadoc

1   /*
2    * $Id: ActionComponent.java 817333 2009-09-21 17:29:44Z wesw $
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  
22  package org.apache.struts2.components;
23  
24  import java.io.IOException;
25  import java.io.Writer;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.Map;
29  
30  import javax.servlet.ServletContext;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  import javax.servlet.jsp.PageContext;
34  
35  import org.apache.struts2.ServletActionContext;
36  import org.apache.struts2.StrutsException;
37  import org.apache.struts2.dispatcher.Dispatcher;
38  import org.apache.struts2.dispatcher.RequestMap;
39  import org.apache.struts2.dispatcher.mapper.ActionMapper;
40  import org.apache.struts2.dispatcher.mapper.ActionMapping;
41  import org.apache.struts2.views.annotations.StrutsTag;
42  import org.apache.struts2.views.annotations.StrutsTagAttribute;
43  import org.apache.struts2.views.jsp.TagUtils;
44  
45  import com.opensymphony.xwork2.ActionContext;
46  import com.opensymphony.xwork2.ActionProxy;
47  import com.opensymphony.xwork2.ActionProxyFactory;
48  import com.opensymphony.xwork2.ActionInvocation;
49  import com.opensymphony.xwork2.inject.Inject;
50  import com.opensymphony.xwork2.util.ValueStack;
51  import com.opensymphony.xwork2.util.ValueStackFactory;
52  import com.opensymphony.xwork2.util.logging.Logger;
53  import com.opensymphony.xwork2.util.logging.LoggerFactory;
54  
55  /***
56   * <!-- START SNIPPET: javadoc -->
57   * <p>This tag enables developers to call actions directly from a JSP page by specifying the action name and an optional
58   * namespace.  The body content of the tag is used to render the results from the Action.  Any result processor defined
59   * for this action in struts.xml will be ignored, <i>unless</i> the executeResult parameter is specified.</p>
60   * <!-- END SNIPPET: javadoc -->
61   *
62   * <!-- START SNIPPET: params -->
63   * <ul>
64   *      <li>id (String) - the id (if specified) to put the action under stack's context.
65   *      <li>name* (String) - name of the action to be executed (without the extension suffix eg. .action)</li>
66   *      <li>namespace (String) - default to the namespace where this action tag is invoked</li>
67   *      <li>executeResult (Boolean) -  default is false. Decides whether the result of this action is to be executed or not</li>
68   *      <li>ignoreContextParams (Boolean) - default to false. Decides whether the request parameters are to be included when the action is invoked</li>
69   * </ul>
70   * <!-- END SNIPPET: params -->
71   *
72   * <pre>
73   * <!-- START SNIPPET: javacode -->
74   * public class ActionTagAction extends ActionSupport {
75   *
76   *  public String execute() throws Exception {
77   *      return "done";
78   *  }
79   *
80   *  public String doDefault() throws Exception {
81   *      ServletActionContext.getRequest().setAttribute("stringByAction", "This is a String put in by the action's doDefault()");
82   *      return "done";
83   *  }
84   * }
85   * <!-- END SNIPPET: javacode -->
86   * </pre>
87   *
88   * <pre>
89   * <!-- START SNIPPET: strutsxml -->
90   *   <xwork>
91   *      ....
92   *     <action name="actionTagAction1" class="tmjee.testing.ActionTagAction">
93   *         <result name="done">success.jsp</result>
94   *     </action>
95   *      <action name="actionTagAction2" class="tmjee.testing.ActionTagAction" method="default">
96   *         <result name="done">success.jsp</result>
97   *     </action>
98   *      ....
99   *   </xwork>
100  * <!-- END SNIPPET: strutsxml -->
101  * </pre>
102  *
103  * <pre>
104  * <!-- START SNIPPET: example -->
105  *  <div>The following action tag will execute result and include it in this page</div>
106  *  <br />
107  *  <s:action name="actionTagAction" executeResult="true" />
108  *  <br />
109  *  <div>The following action tag will do the same as above, but invokes method specialMethod in action</div>
110  *  <br />
111  *  <s:action name="actionTagAction!specialMethod" executeResult="true" />
112  *  <br />
113  *  <div>The following action tag will not execute result, but put a String in request scope
114  *       under an id "stringByAction" which will be retrieved using property tag</div>
115  *  <s:action name="actionTagAction!default" executeResult="false" />
116  *  <s:property value="#attr.stringByAction" />
117  * <!-- END SNIPPET: example -->
118  * </pre>
119  *
120  */
121 @StrutsTag(name="action", tldTagClass="org.apache.struts2.views.jsp.ActionTag", description="Execute an action from within a view")
122 public class ActionComponent extends ContextBean {
123     private static final Logger LOG = LoggerFactory.getLogger(ActionComponent.class);
124 
125     protected HttpServletResponse res;
126     protected HttpServletRequest req;
127 
128     protected ValueStackFactory valueStackFactory;
129     protected ActionProxyFactory actionProxyFactory;
130     protected ActionProxy proxy;
131     protected String name;
132     protected String namespace;
133     protected boolean executeResult;
134     protected boolean ignoreContextParams;
135     protected boolean flush = true;
136     protected boolean rethrowException;
137 
138     public ActionComponent(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
139         super(stack);
140         this.req = req;
141         this.res = res;
142     }
143 
144     /***
145      * @param actionProxyFactory the actionProxyFactory to set
146      */
147     @Inject
148     public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
149         this.actionProxyFactory = actionProxyFactory;
150     }
151     
152     @Inject
153     public void setValueStackFactory(ValueStackFactory valueStackFactory) {
154         this.valueStackFactory = valueStackFactory;
155     }
156 
157     @Inject
158     public void setActionMapper(ActionMapper mapper) {
159         this.actionMapper = mapper;
160     }
161 
162     public boolean end(Writer writer, String body) {
163         boolean end = super.end(writer, "", false);
164         try {
165             if (flush) {
166                 try {
167                     writer.flush();
168                 } catch (IOException e) {
169                     LOG.warn("error while trying to flush writer ", e);
170                 }
171             }
172             executeAction();
173 
174             if ((getVar() != null) && (proxy != null)) {
175                 getStack().setValue("#attr['" + getVar() + "']",
176                         proxy.getAction());
177             }
178         } finally {
179             popComponentStack();
180         }
181         return end;
182     }
183 
184     protected Map createExtraContext() {
185         Map newParams = createParametersForContext();
186 
187         ActionContext ctx = new ActionContext(stack.getContext());
188         ServletContext servletContext = (ServletContext) ctx.get(ServletActionContext.SERVLET_CONTEXT);
189         PageContext pageContext = (PageContext) ctx.get(ServletActionContext.PAGE_CONTEXT);
190         Map session = ctx.getSession();
191         Map application = ctx.getApplication();
192 
193         Dispatcher du = Dispatcher.getInstance();
194         Map<String, Object> extraContext = du.createContextMap(new RequestMap(req),
195                 newParams,
196                 session,
197                 application,
198                 req,
199                 res,
200                 servletContext);
201 
202         ValueStack newStack = valueStackFactory.createValueStack(stack);
203         extraContext.put(ActionContext.VALUE_STACK, newStack);
204 
205         // add page context, such that ServletDispatcherResult will do an include
206         extraContext.put(ServletActionContext.PAGE_CONTEXT, pageContext);
207 
208         return extraContext;
209     }
210 
211     /***
212      * Creates parameters map using parameters from the value stack and component parameters.  Any non-String array
213      * values will be converted into a single-value String array.
214      * 
215      * @return A map of String[] parameters
216      */
217     protected Map<String,String[]> createParametersForContext() {
218         Map parentParams = null;
219 
220         if (!ignoreContextParams) {
221             parentParams = new ActionContext(getStack().getContext()).getParameters();
222         }
223 
224         Map<String,String[]> newParams = (parentParams != null) 
225             ? new HashMap<String,String[]>(parentParams) 
226             : new HashMap<String,String[]>();
227 
228         if (parameters != null) {
229             Map<String,String[]> params = new HashMap<String,String[]>();
230             for (Iterator i = parameters.entrySet().iterator(); i.hasNext(); ) {
231                 Map.Entry entry = (Map.Entry) i.next();
232                 String key = (String) entry.getKey();
233                 Object val = entry.getValue();
234                 if (val.getClass().isArray() && String.class == val.getClass().getComponentType()) {
235                     params.put(key, (String[])val);
236                 } else {
237                     params.put(key, new String[]{val.toString()});
238                 }
239             }
240             newParams.putAll(params);
241         }
242         return newParams;
243     }
244 
245     public ActionProxy getProxy() {
246         return proxy;
247     }
248 
249     /***
250      * Execute the requested action.  If no namespace is provided, we'll
251      * attempt to derive a namespace using buildNamespace().  The ActionProxy
252      * and the namespace will be saved into the instance variables proxy and
253      * namespace respectively.
254      *
255      * @see org.apache.struts2.views.jsp.TagUtils#buildNamespace
256      */
257     protected void executeAction() {
258         String actualName = findString(name, "name", "Action name is required. Example: updatePerson");
259 
260         if (actualName == null) {
261             throw new StrutsException("Unable to find value for name " + name);
262         }
263 
264         // handle "name!method" convention.
265         final String actionName;
266         final String methodName;
267 
268         ActionMapping mapping = actionMapper.getMappingFromActionName(actualName);
269         actionName = mapping.getName();
270         methodName = mapping.getMethod();
271 
272         String namespace;
273 
274         if (this.namespace == null) {
275             namespace = TagUtils.buildNamespace(actionMapper, getStack(), req);
276         } else {
277             namespace = findString(this.namespace);
278         }
279 
280         // get the old value stack from the request
281         ValueStack stack = getStack();
282         // execute at this point, after params have been set
283         ActionInvocation inv = ActionContext.getContext().getActionInvocation();
284         try {
285 
286             proxy = actionProxyFactory.createActionProxy(namespace, actionName, methodName, createExtraContext(), executeResult, true);
287             // set the new stack into the request for the taglib to use
288             req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
289             proxy.execute();
290 
291         } catch (Exception e) {
292             String message = "Could not execute action: " + namespace + "/" + actualName;
293             LOG.error(message, e);
294             if (rethrowException) {
295                 throw new StrutsException(message, e);
296             }
297         } finally {
298             // set the old stack back on the request
299             req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
300             if (inv != null) {
301                 ActionContext.getContext().setActionInvocation(inv);
302             }
303         }
304 
305         if ((getVar() != null) && (proxy != null)) {
306             putInContext(proxy.getAction());
307         }
308     }
309 
310     @StrutsTagAttribute(required=true,description="Name of the action to be executed (without the extension suffix eg. .action)")
311     public void setName(String name) {
312         this.name = name;
313     }
314 
315     @StrutsTagAttribute(description="Namespace for action to call", defaultValue="namespace from where tag is used")
316     public void setNamespace(String namespace) {
317         this.namespace = namespace;
318     }
319 
320     @StrutsTagAttribute(description="Whether the result of this action (probably a view) should be executed/rendered", type="Boolean", defaultValue="false")
321     public void setExecuteResult(boolean executeResult) {
322         this.executeResult = executeResult;
323     }
324 
325     @StrutsTagAttribute(description="Whether the request parameters are to be included when the action is invoked", type="Boolean", defaultValue="false")
326     public void setIgnoreContextParams(boolean ignoreContextParams) {
327         this.ignoreContextParams = ignoreContextParams;
328     }
329 
330     @StrutsTagAttribute(description="Whether the writer should be flush upon end of action component tag, default to true", type="Boolean", defaultValue="true")
331     public void setFlush(boolean flush) {
332         this.flush = flush;
333     }
334 
335     @StrutsTagAttribute(description="Whether an exception should be rethrown, if the target action throws an exception", type="Boolean", defaultValue="false")
336     public void setRethrowException(boolean rethrowException) {
337         this.rethrowException = rethrowException;
338     }
339 }