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