1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 ActionMapper actionMapper;
132 protected String name;
133 protected String namespace;
134 protected boolean executeResult;
135 protected boolean ignoreContextParams;
136 protected boolean flush = true;
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
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 private 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
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
281 ValueStack stack = getStack();
282
283 ActionInvocation inv = ActionContext.getContext().getActionInvocation();
284 try {
285
286 proxy = actionProxyFactory.createActionProxy(namespace, actionName, methodName, createExtraContext(), executeResult, true);
287
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 } finally {
295
296 req.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
297 if (inv != null) {
298 ActionContext.getContext().setActionInvocation(inv);
299 }
300 }
301
302 if ((getVar() != null) && (proxy != null)) {
303 putInContext(proxy.getAction());
304 }
305 }
306
307 @StrutsTagAttribute(required=true,description="Name of the action to be executed (without the extension suffix eg. .action)")
308 public void setName(String name) {
309 this.name = name;
310 }
311
312 @StrutsTagAttribute(description="Namespace for action to call", defaultValue="namespace from where tag is used")
313 public void setNamespace(String namespace) {
314 this.namespace = namespace;
315 }
316
317 @StrutsTagAttribute(description="Whether the result of this action (probably a view) should be executed/rendered", type="Boolean", defaultValue="false")
318 public void setExecuteResult(boolean executeResult) {
319 this.executeResult = executeResult;
320 }
321
322 @StrutsTagAttribute(description="Whether the request parameters are to be included when the action is invoked", type="Boolean", defaultValue="false")
323 public void setIgnoreContextParams(boolean ignoreContextParams) {
324 this.ignoreContextParams = ignoreContextParams;
325 }
326
327 @StrutsTagAttribute(description="Whether the writer should be flush upon end of action component tag, default to true", type="Boolean", defaultValue="true")
328 public void setFlush(boolean flush) {
329 this.flush = flush;
330 }
331 }