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.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
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
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
280 ValueStack stack = getStack();
281
282 try {
283
284 proxy = actionProxyFactory.createActionProxy(namespace, actionName, methodName, createExtraContext(), executeResult, true);
285
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
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 }