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.action.ActionForm;
23 import org.apache.struts.action.ActionForward;
24 import org.apache.struts.action.ActionMapping;
25
26 import javax.servlet.ServletException;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32
33 import java.util.HashMap;
34
35 /***
36 * <p>An abstract <strong>Action</strong> that dispatches to a public method
37 * that is named by the request parameter whose name is specified by the
38 * <code>parameter</code> property of the corresponding ActionMapping. This
39 * Action is useful for developers who prefer to combine many similar actions
40 * into a single Action class, in order to simplify their application
41 * design.</p>
42 *
43 * <p>To configure the use of this action in your <code>struts-config.xml</code>
44 * file, create an entry like this:</p>
45 *
46 * <code> <action path="/saveSubscription" type="org.apache.struts.actions.DispatchAction"
47 * name="subscriptionForm" scope="request" input="/subscription.jsp"
48 * parameter="method"/> </code>
49 *
50 * <p>which will use the value of the request parameter named "method" to pick
51 * the appropriate "execute" method, which must have the same signature (other
52 * than method name) of the standard Action.execute method. For example, you
53 * might have the following three methods in the same action:</p>
54 *
55 * <ul>
56 *
57 * <li>public ActionForward delete(ActionMapping mapping, ActionForm form,
58 * HttpServletRequest request, HttpServletResponse response) throws
59 * Exception</li>
60 *
61 * <li>public ActionForward insert(ActionMapping mapping, ActionForm form,
62 * HttpServletRequest request, HttpServletResponse response) throws
63 * Exception</li>
64 *
65 * <li>public ActionForward update(ActionMapping mapping, ActionForm form,
66 * HttpServletRequest request, HttpServletResponse response) throws
67 * Exception</li>
68 *
69 * </ul>
70 *
71 * <p>and call one of the methods with a URL like this:</p>
72 *
73 * <p> <code> http://localhost:8080/myapp/saveSubscription.do?method=update
74 * </code></p>
75 *
76 * <p><strong>NOTE</strong> - All of the other mapping characteristics of this
77 * action must be shared by the various handlers. This places some
78 * constraints over what types of handlers may reasonably be packaged into the
79 * same <code>DispatchAction</code> subclass.</p>
80 *
81 * <p><strong>NOTE</strong> - If the value of the request parameter is empty,
82 * a method named <code>unspecified</code> is called. The default action is to
83 * throw an exception. If the request was cancelled (a
84 * <code>html:cancel</code> button was pressed), the custom handler
85 * <code>cancelled</code> will be used instead. You can also override the
86 * <code>getMethodName</code> method to override the action's default handler
87 * selection.</p>
88 *
89 * @version $Rev: 384088 $ $Date: 2006-03-07 18:49:48 -0700 (Tue, 07 Mar 2006) $
90 */
91 public abstract class DispatchAction extends BaseAction {
92 /***
93 * Commons Logging instance.
94 */
95 protected static Log log = LogFactory.getLog(DispatchAction.class);
96
97
98
99 /***
100 * The Class instance of this <code>DispatchAction</code> class.
101 */
102 protected Class clazz = this.getClass();
103
104 /***
105 * The set of Method objects we have introspected for this class, keyed by
106 * method name. This collection is populated as different methods are
107 * called, so that introspection needs to occur only once per method
108 * name.
109 */
110 protected HashMap methods = new HashMap();
111
112 /***
113 * The set of argument type classes for the reflected method call. These
114 * are the same for all calls, so calculate them only once.
115 */
116 protected Class[] types =
117 {
118 ActionMapping.class, ActionForm.class, HttpServletRequest.class,
119 HttpServletResponse.class
120 };
121
122
123
124 /***
125 * Process the specified HTTP request, and create the corresponding HTTP
126 * response (or forward to another web component that will create it).
127 * Return an <code>ActionForward</code> instance describing where and how
128 * control should be forwarded, or <code>null</code> if the response has
129 * already been completed.
130 *
131 * @param mapping The ActionMapping used to select this instance
132 * @param form The optional ActionForm bean for this request (if any)
133 * @param request The HTTP request we are processing
134 * @param response The HTTP response we are creating
135 * @return The forward to which control should be transferred, or
136 * <code>null</code> if the response has been completed.
137 * @throws Exception if an exception occurs
138 */
139 public ActionForward execute(ActionMapping mapping, ActionForm form,
140 HttpServletRequest request, HttpServletResponse response)
141 throws Exception {
142 if (isCancelled(request)) {
143 ActionForward af = cancelled(mapping, form, request, response);
144
145 if (af != null) {
146 return af;
147 }
148 }
149
150
151 String parameter = getParameter(mapping, form, request, response);
152
153
154 String name =
155 getMethodName(mapping, form, request, response, parameter);
156
157
158 if ("execute".equals(name) || "perform".equals(name)) {
159 String message =
160 messages.getMessage("dispatch.recursive", mapping.getPath());
161
162 log.error(message);
163 throw new ServletException(message);
164 }
165
166
167 return dispatchMethod(mapping, form, request, response, name);
168 }
169
170 /***
171 * Method which is dispatched to when there is no value for specified
172 * request parameter included in the request. Subclasses of
173 * <code>DispatchAction</code> should override this method if they wish to
174 * provide default behavior different than throwing a ServletException.
175 *
176 * @param mapping The ActionMapping used to select this instance
177 * @param form The optional ActionForm bean for this request (if any)
178 * @param request The non-HTTP request we are processing
179 * @param response The non-HTTP response we are creating
180 * @return The forward to which control should be transferred, or
181 * <code>null</code> if the response has been completed.
182 * @throws Exception if the application business logic throws an
183 * exception.
184 */
185 protected ActionForward unspecified(ActionMapping mapping, ActionForm form,
186 HttpServletRequest request, HttpServletResponse response)
187 throws Exception {
188 String message =
189 messages.getMessage("dispatch.parameter", mapping.getPath(),
190 mapping.getParameter());
191
192 log.error(message);
193
194 throw new ServletException(message);
195 }
196
197 /***
198 * Method which is dispatched to when the request is a cancel button
199 * submit. Subclasses of <code>DispatchAction</code> should override this
200 * method if they wish to provide default behavior different than
201 * returning null.
202 *
203 * @param mapping The ActionMapping used to select this instance
204 * @param form The optional ActionForm bean for this request (if any)
205 * @param request The non-HTTP request we are processing
206 * @param response The non-HTTP response we are creating
207 * @return The forward to which control should be transferred, or
208 * <code>null</code> if the response has been completed.
209 * @throws Exception if the application business logic throws an
210 * exception.
211 * @since Struts 1.2.0
212 */
213 protected ActionForward cancelled(ActionMapping mapping, ActionForm form,
214 HttpServletRequest request, HttpServletResponse response)
215 throws Exception {
216 return null;
217 }
218
219
220
221 /***
222 * Dispatch to the specified method.
223 *
224 * @param mapping The ActionMapping used to select this instance
225 * @param form The optional ActionForm bean for this request (if any)
226 * @param request The non-HTTP request we are processing
227 * @param response The non-HTTP response we are creating
228 * @param name The name of the method to invoke
229 * @return The forward to which control should be transferred, or
230 * <code>null</code> if the response has been completed.
231 * @throws Exception if the application business logic throws an
232 * exception.
233 * @since Struts 1.1
234 */
235 protected ActionForward dispatchMethod(ActionMapping mapping,
236 ActionForm form, HttpServletRequest request,
237 HttpServletResponse response, String name)
238 throws Exception {
239
240
241 if (name == null) {
242 return this.unspecified(mapping, form, request, response);
243 }
244
245
246 Method method = null;
247
248 try {
249 method = getMethod(name);
250 } catch (NoSuchMethodException e) {
251 String message =
252 messages.getMessage("dispatch.method", mapping.getPath(), name);
253
254 log.error(message, e);
255
256 String userMsg =
257 messages.getMessage("dispatch.method.user", mapping.getPath());
258 throw new NoSuchMethodException(userMsg);
259 }
260
261 ActionForward forward = null;
262
263 try {
264 Object[] args = { mapping, form, request, response };
265
266 forward = (ActionForward) method.invoke(this, args);
267 } catch (ClassCastException e) {
268 String message =
269 messages.getMessage("dispatch.return", mapping.getPath(), name);
270
271 log.error(message, e);
272 throw e;
273 } catch (IllegalAccessException e) {
274 String message =
275 messages.getMessage("dispatch.error", mapping.getPath(), name);
276
277 log.error(message, e);
278 throw e;
279 } catch (InvocationTargetException e) {
280
281
282 Throwable t = e.getTargetException();
283
284 if (t instanceof Exception) {
285 throw ((Exception) t);
286 } else {
287 String message =
288 messages.getMessage("dispatch.error", mapping.getPath(),
289 name);
290
291 log.error(message, e);
292 throw new ServletException(t);
293 }
294 }
295
296
297 return (forward);
298 }
299
300 /***
301 * <p>Returns the parameter value.</p>
302 *
303 * @param mapping The ActionMapping used to select this instance
304 * @param form The optional ActionForm bean for this request (if any)
305 * @param request The HTTP request we are processing
306 * @param response The HTTP response we are creating
307 * @return The <code>ActionMapping</code> parameter's value
308 * @throws Exception if the parameter is missing.
309 */
310 protected String getParameter(ActionMapping mapping, ActionForm form,
311 HttpServletRequest request, HttpServletResponse response)
312 throws Exception {
313
314
315 String parameter = mapping.getParameter();
316
317 if (parameter == null) {
318 String message =
319 messages.getMessage("dispatch.handler", mapping.getPath());
320
321 log.error(message);
322
323 throw new ServletException(message);
324 }
325
326
327 return parameter;
328 }
329
330 /***
331 * Introspect the current class to identify a method of the specified name
332 * that accepts the same parameter types as the <code>execute</code>
333 * method does.
334 *
335 * @param name Name of the method to be introspected
336 * @return The method with the specified name.
337 * @throws NoSuchMethodException if no such method can be found
338 */
339 protected Method getMethod(String name)
340 throws NoSuchMethodException {
341 synchronized (methods) {
342 Method method = (Method) methods.get(name);
343
344 if (method == null) {
345 method = clazz.getMethod(name, types);
346 methods.put(name, method);
347 }
348
349 return (method);
350 }
351 }
352
353 /***
354 * Returns the method name, given a parameter's value.
355 *
356 * @param mapping The ActionMapping used to select this instance
357 * @param form The optional ActionForm bean for this request (if
358 * any)
359 * @param request The HTTP request we are processing
360 * @param response The HTTP response we are creating
361 * @param parameter The <code>ActionMapping</code> parameter's name
362 * @return The method's name.
363 * @throws Exception if an error occurs.
364 * @since Struts 1.2.0
365 */
366 protected String getMethodName(ActionMapping mapping, ActionForm form,
367 HttpServletRequest request, HttpServletResponse response,
368 String parameter) throws Exception {
369
370
371 return request.getParameter(parameter);
372 }
373 }