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