View Javadoc

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