1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts.actions;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25 import org.apache.struts.Globals;
26 import org.apache.struts.action.Action;
27 import org.apache.struts.action.ActionForm;
28 import org.apache.struts.action.ActionForward;
29 import org.apache.struts.action.ActionMapping;
30 import org.apache.struts.util.MessageResources;
31
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35
36 import java.lang.reflect.InvocationTargetException;
37 import java.lang.reflect.Method;
38
39 import java.util.HashMap;
40
41 /**
42 * <p>Action <i>helper</i> class that dispatches to a public method in an
43 * Action.</p> <p/> <p>This class is provided as an alternative mechanism to
44 * using DispatchAction and its various flavours and means <i>Dispatch</i>
45 * behaviour can be easily implemented into any <code>Action</code> without
46 * having to inherit from a particular super <code>Action</code>.</p> <p/>
47 * <p>To implement <i>dispatch</i> behaviour in an <code>Action</code> class,
48 * create your custom Action as follows, along with the methods you require
49 * (and optionally "cancelled" and "unspecified" methods):</p> <p/>
50 * <pre>
51 * public class MyCustomAction extends Action {
52 *
53 * protected ActionDispatcher dispatcher
54 * = new ActionDispatcher(this, ActionDispatcher.MAPPING_FLAVOR);
55 *
56 * public ActionForward execute(ActionMapping mapping,
57 * ActionForm form,
58 * HttpServletRequest request,
59 * HttpServletResponse response)
60 * throws Exception {
61 * return dispatcher.execute(mapping, form, request, response);
62 * }
63 * }
64 * </pre>
65 * <p/>
66 *
67 * <p>It provides three flavours of determing the name of the method:</p>
68 *
69 * <ul>
70 *
71 * <li><strong>{@link #DEFAULT_FLAVOR}</strong> - uses the parameter
72 * specified in the struts-config.xml to get the method name from the Request
73 * (equivalent to <code>DispatchAction</code> <b>except</b> uses "method" as a
74 * default if the <code>parameter</code> is not specified in the
75 * struts-config.xml).</li>
76 *
77 * <li><strong>{@link #DISPATCH_FLAVOR}</strong>
78 * - uses the parameter specified in the struts-config.xml to get the method
79 * name from the Request (equivalent to <code>DispatchAction</code>).</li>
80 *
81 * <li><strong>{@link #MAPPING_FLAVOR}</strong> - uses the parameter
82 * specified in the struts-config.xml as the method name (equivalent to
83 * <code>MappingDispatchAction</code>).</li>
84
85 * </ul>
86 *
87 * @version $Rev: 471754 $ $Date: 2006-11-06 08:55:09 -0600 (Mon, 06 Nov 2006) $
88 * @since Struts 1.2.7
89 */
90 public class ActionDispatcher {
91
92
93 /**
94 * Indicates "default" dispatch flavor.
95 */
96 public static final int DEFAULT_FLAVOR = 0;
97
98 /**
99 * Indicates "mapping" dispatch flavor.
100 */
101 public static final int MAPPING_FLAVOR = 1;
102
103 /**
104 * Indicates flavor compatible with DispatchAction.
105 */
106 public static final int DISPATCH_FLAVOR = 2;
107
108 /**
109 * Commons Logging instance.
110 */
111 protected static Log log = LogFactory.getLog(ActionDispatcher.class);
112
113 /**
114 * The message resources for this package.
115 */
116 protected static MessageResources messages =
117 MessageResources.getMessageResources(
118 "org.apache.struts.actions.LocalStrings");
119
120 /**
121 * The associated Action to dispatch to.
122 */
123 protected Action actionInstance;
124
125 /**
126 * Indicates dispatch <i>flavor</i>.
127 */
128 protected int flavor;
129
130 /**
131 * The Class instance of this <code>DispatchAction</code> class.
132 */
133 protected Class clazz;
134
135 /**
136 * The set of Method objects we have introspected for this class, keyed by
137 * method name. This collection is populated as different methods are
138 * called, so that introspection needs to occur only once per method
139 * name.
140 */
141 protected HashMap methods = new HashMap();
142
143 /**
144 * The set of argument type classes for the reflected method call. These
145 * are the same for all calls, so calculate them only once.
146 */
147 protected Class[] types =
148 {
149 ActionMapping.class, ActionForm.class, HttpServletRequest.class,
150 HttpServletResponse.class
151 };
152
153
154
155 /**
156 * Construct an instance of this class from the supplied parameters.
157 *
158 * @param actionInstance The action instance to be invoked.
159 */
160 public ActionDispatcher(Action actionInstance) {
161 this(actionInstance, DEFAULT_FLAVOR);
162 }
163
164 /**
165 * Construct an instance of this class from the supplied parameters.
166 *
167 * @param actionInstance The action instance to be invoked.
168 * @param flavor The flavor of dispatch to use.
169 */
170 public ActionDispatcher(Action actionInstance, int flavor) {
171 this.actionInstance = actionInstance;
172 this.flavor = flavor;
173
174 clazz = actionInstance.getClass();
175 }
176
177
178
179 /**
180 * Process the specified HTTP request, and create the corresponding HTTP
181 * response (or forward to another web component that will create it).
182 * Return an <code>ActionForward</code> instance describing where and how
183 * control should be forwarded, or <code>null</code> if the response has
184 * already been completed.
185 *
186 * @param mapping The ActionMapping used to select this instance
187 * @param form The optional ActionForm bean for this request (if any)
188 * @param request The HTTP request we are processing
189 * @param response The HTTP response we are creating
190 * @return The forward to which control should be transferred, or
191 * <code>null</code> if the response has been completed.
192 * @throws Exception if an exception occurs
193 */
194 public ActionForward execute(ActionMapping mapping, ActionForm form,
195 HttpServletRequest request, HttpServletResponse response)
196 throws Exception {
197
198 if (isCancelled(request)) {
199 ActionForward af = cancelled(mapping, form, request, response);
200
201 if (af != null) {
202 return af;
203 }
204 }
205
206
207 String parameter = getParameter(mapping, form, request, response);
208
209
210 String name =
211 getMethodName(mapping, form, request, response, parameter);
212
213
214 if ("execute".equals(name) || "perform".equals(name)) {
215 String message =
216 messages.getMessage("dispatch.recursive", mapping.getPath());
217
218 log.error(message);
219 throw new ServletException(message);
220 }
221
222
223 return dispatchMethod(mapping, form, request, response, name);
224 }
225
226 /**
227 * <p>Dispatches to the target class' <code>unspecified</code> method, if
228 * present, otherwise throws a ServletException. Classes utilizing
229 * <code>ActionDispatcher</code> should provide an <code>unspecified</code>
230 * method if they wish to provide behavior different than throwing a
231 * ServletException.</p>
232 *
233 * @param mapping The ActionMapping used to select this instance
234 * @param form The optional ActionForm bean for this request (if any)
235 * @param request The non-HTTP request we are processing
236 * @param response The non-HTTP response we are creating
237 * @return The forward to which control should be transferred, or
238 * <code>null</code> if the response has been completed.
239 * @throws Exception if the application business logic throws an
240 * exception.
241 */
242 protected ActionForward unspecified(ActionMapping mapping, ActionForm form,
243 HttpServletRequest request, HttpServletResponse response)
244 throws Exception {
245
246 String name = "unspecified";
247 Method method = null;
248
249 try {
250 method = getMethod(name);
251 } catch (NoSuchMethodException e) {
252 String message =
253 messages.getMessage("dispatch.parameter", mapping.getPath(),
254 mapping.getParameter());
255
256 log.error(message);
257
258 throw new ServletException(message);
259 }
260
261 return dispatchMethod(mapping, form, request, response, name, method);
262 }
263
264 /**
265 * <p>Dispatches to the target class' cancelled method, if present,
266 * otherwise returns null. Classes utilizing <code>ActionDispatcher</code>
267 * should provide a <code>cancelled</code> method if they wish to provide
268 * behavior different than returning null.</p>
269 *
270 * @param mapping The ActionMapping used to select this instance
271 * @param form The optional ActionForm bean for this request (if any)
272 * @param request The non-HTTP request we are processing
273 * @param response The non-HTTP response we are creating
274 * @return The forward to which control should be transferred, or
275 * <code>null</code> if the response has been completed.
276 * @throws Exception if the application business logic throws an
277 * exception.
278 */
279 protected ActionForward cancelled(ActionMapping mapping, ActionForm form,
280 HttpServletRequest request, HttpServletResponse response)
281 throws Exception {
282
283 String name = "cancelled";
284 Method method = null;
285
286 try {
287 method = getMethod(name);
288 } catch (NoSuchMethodException e) {
289 return null;
290 }
291
292 return dispatchMethod(mapping, form, request, response, name, method);
293 }
294
295
296
297 /**
298 * Dispatch to the specified method.
299 *
300 * @param mapping The ActionMapping used to select this instance
301 * @param form The optional ActionForm bean for this request (if any)
302 * @param request The non-HTTP request we are processing
303 * @param response The non-HTTP response we are creating
304 * @param name The name of the method to invoke
305 * @return The forward to which control should be transferred, or
306 * <code>null</code> if the response has been completed.
307 * @throws Exception if the application business logic throws an
308 * exception.
309 */
310 protected ActionForward dispatchMethod(ActionMapping mapping,
311 ActionForm form, HttpServletRequest request,
312 HttpServletResponse response, String name)
313 throws Exception {
314
315
316 if (name == null) {
317 return this.unspecified(mapping, form, request, response);
318 }
319
320
321 Method method = null;
322
323 try {
324 method = getMethod(name);
325 } catch (NoSuchMethodException e) {
326 String message =
327 messages.getMessage("dispatch.method", mapping.getPath(), name);
328
329 log.error(message, e);
330
331 String userMsg =
332 messages.getMessage("dispatch.method.user", mapping.getPath());
333 throw new NoSuchMethodException(userMsg);
334 }
335
336 return dispatchMethod(mapping, form, request, response, name, method);
337 }
338
339 /**
340 * Dispatch to the specified method.
341 *
342 * @param mapping The ActionMapping used to select this instance
343 * @param form The optional ActionForm bean for this request (if any)
344 * @param request The non-HTTP request we are processing
345 * @param response The non-HTTP response we are creating
346 * @param name The name of the method to invoke
347 * @param method The method to invoke
348 * @return The forward to which control should be transferred, or
349 * <code>null</code> if the response has been completed.
350 * @throws Exception if the application business logic throws an
351 * exception.
352 */
353 protected ActionForward dispatchMethod(ActionMapping mapping,
354 ActionForm form, HttpServletRequest request,
355 HttpServletResponse response, String name, Method method)
356 throws Exception {
357 ActionForward forward = null;
358
359 try {
360 Object[] args = { mapping, form, request, response };
361
362 forward = (ActionForward) method.invoke(actionInstance, args);
363 } catch (ClassCastException e) {
364 String message =
365 messages.getMessage("dispatch.return", mapping.getPath(), name);
366
367 log.error(message, e);
368 throw e;
369 } catch (IllegalAccessException e) {
370 String message =
371 messages.getMessage("dispatch.error", mapping.getPath(), name);
372
373 log.error(message, e);
374 throw e;
375 } catch (InvocationTargetException e) {
376
377
378 Throwable t = e.getTargetException();
379
380 if (t instanceof Exception) {
381 throw ((Exception) t);
382 } else {
383 String message =
384 messages.getMessage("dispatch.error", mapping.getPath(),
385 name);
386
387 log.error(message, e);
388 throw new ServletException(t);
389 }
390 }
391
392
393 return (forward);
394 }
395
396 /**
397 * Introspect the current class to identify a method of the specified name
398 * that accepts the same parameter types as the <code>execute</code>
399 * method does.
400 *
401 * @param name Name of the method to be introspected
402 * @return The method with the specified name.
403 * @throws NoSuchMethodException if no such method can be found
404 */
405 protected Method getMethod(String name)
406 throws NoSuchMethodException {
407 synchronized (methods) {
408 Method method = (Method) methods.get(name);
409
410 if (method == null) {
411 method = clazz.getMethod(name, types);
412 methods.put(name, method);
413 }
414
415 return (method);
416 }
417 }
418
419 /**
420 * <p>Returns the parameter value as influenced by the selected {@link
421 * #flavor} specified for this <code>ActionDispatcher</code>.</p>
422 *
423 * @param mapping The ActionMapping used to select this instance
424 * @param form The optional ActionForm bean for this request (if any)
425 * @param request The HTTP request we are processing
426 * @param response The HTTP response we are creating
427 * @return The <code>ActionMapping</code> parameter's value
428 * @throws Exception if an error occurs.
429 */
430 protected String getParameter(ActionMapping mapping, ActionForm form,
431 HttpServletRequest request, HttpServletResponse response)
432 throws Exception {
433 String parameter = mapping.getParameter();
434
435 if ("".equals(parameter)) {
436 parameter = null;
437 }
438
439 if ((parameter == null) && (flavor == DEFAULT_FLAVOR)) {
440
441 return "method";
442 }
443
444 if ((parameter == null)
445 && ((flavor == MAPPING_FLAVOR) || (flavor == DISPATCH_FLAVOR))) {
446 String message =
447 messages.getMessage("dispatch.handler", mapping.getPath());
448
449 log.error(message);
450
451 throw new ServletException(message);
452 }
453
454 return parameter;
455 }
456
457 /**
458 * Returns the method name, given a parameter's value.
459 *
460 * @param mapping The ActionMapping used to select this instance
461 * @param form The optional ActionForm bean for this request (if
462 * any)
463 * @param request The HTTP request we are processing
464 * @param response The HTTP response we are creating
465 * @param parameter The <code>ActionMapping</code> parameter's name
466 * @return The method's name.
467 * @throws Exception if an error occurs.
468 */
469 protected String getMethodName(ActionMapping mapping, ActionForm form,
470 HttpServletRequest request, HttpServletResponse response,
471 String parameter) throws Exception {
472
473 if (flavor == MAPPING_FLAVOR) {
474 return parameter;
475 }
476
477
478 return request.getParameter(parameter);
479 }
480
481 /**
482 * <p>Returns <code>true</code> if the current form's cancel button was
483 * pressed. This method will check if the <code>Globals.CANCEL_KEY</code>
484 * request attribute has been set, which normally occurs if the cancel
485 * button generated by <strong>CancelTag</strong> was pressed by the user
486 * in the current request. If <code>true</code>, validation performed by
487 * an <strong>ActionForm</strong>'s <code>validate()</code> method will
488 * have been skipped by the controller servlet.</p>
489 *
490 * @param request The servlet request we are processing
491 * @return <code>true</code> if the current form's cancel button was
492 * pressed; <code>false</code> otherwise.
493 * @see org.apache.struts.taglib.html.CancelTag
494 */
495 protected boolean isCancelled(HttpServletRequest request) {
496 return (request.getAttribute(Globals.CANCEL_KEY) != null);
497 }
498 }