View Javadoc

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