1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
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
245 ValueStack stack = getStack();
246
247 try {
248
249 proxy = actionProxyFactory.createActionProxy(namespace, actionName, createExtraContext(), executeResult, true);
250 if (null != methodName) {
251 proxy.setMethod(methodName);
252 }
253
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
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 }