001: /*
002: * $Id: ActionDispatcher.java 471754 2006-11-06 14:55:09Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts.actions;
022:
023: import org.apache.commons.logging.Log;
024: import org.apache.commons.logging.LogFactory;
025: import org.apache.struts.Globals;
026: import org.apache.struts.action.Action;
027: import org.apache.struts.action.ActionForm;
028: import org.apache.struts.action.ActionForward;
029: import org.apache.struts.action.ActionMapping;
030: import org.apache.struts.util.MessageResources;
031:
032: import javax.servlet.ServletException;
033: import javax.servlet.http.HttpServletRequest;
034: import javax.servlet.http.HttpServletResponse;
035:
036: import java.lang.reflect.InvocationTargetException;
037: import java.lang.reflect.Method;
038:
039: import java.util.HashMap;
040:
041: /**
042: * <p>Action <i>helper</i> class that dispatches to a public method in an
043: * Action.</p> <p/> <p>This class is provided as an alternative mechanism to
044: * using DispatchAction and its various flavours and means <i>Dispatch</i>
045: * behaviour can be easily implemented into any <code>Action</code> without
046: * having to inherit from a particular super <code>Action</code>.</p> <p/>
047: * <p>To implement <i>dispatch</i> behaviour in an <code>Action</code> class,
048: * create your custom Action as follows, along with the methods you require
049: * (and optionally "cancelled" and "unspecified" methods):</p> <p/>
050: * <pre>
051: * public class MyCustomAction extends Action {
052: *
053: * protected ActionDispatcher dispatcher
054: * = new ActionDispatcher(this, ActionDispatcher.MAPPING_FLAVOR);
055: *
056: * public ActionForward execute(ActionMapping mapping,
057: * ActionForm form,
058: * HttpServletRequest request,
059: * HttpServletResponse response)
060: * throws Exception {
061: * return dispatcher.execute(mapping, form, request, response);
062: * }
063: * }
064: * </pre>
065: * <p/>
066: *
067: * <p>It provides three flavours of determing the name of the method:</p>
068: *
069: * <ul>
070: *
071: * <li><strong>{@link #DEFAULT_FLAVOR}</strong> - uses the parameter
072: * specified in the struts-config.xml to get the method name from the Request
073: * (equivalent to <code>DispatchAction</code> <b>except</b> uses "method" as a
074: * default if the <code>parameter</code> is not specified in the
075: * struts-config.xml).</li>
076: *
077: * <li><strong>{@link #DISPATCH_FLAVOR}</strong>
078: * - uses the parameter specified in the struts-config.xml to get the method
079: * name from the Request (equivalent to <code>DispatchAction</code>).</li>
080: *
081: * <li><strong>{@link #MAPPING_FLAVOR}</strong> - uses the parameter
082: * specified in the struts-config.xml as the method name (equivalent to
083: * <code>MappingDispatchAction</code>).</li>
084:
085: * </ul>
086: *
087: * @version $Rev: 471754 $ $Date: 2006-11-06 08:55:09 -0600 (Mon, 06 Nov 2006) $
088: * @since Struts 1.2.7
089: */
090: public class ActionDispatcher {
091: // ----------------------------------------------------- Instance Variables
092:
093: /**
094: * Indicates "default" dispatch flavor.
095: */
096: public static final int DEFAULT_FLAVOR = 0;
097:
098: /**
099: * 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
112: .getLog(ActionDispatcher.class);
113:
114: /**
115: * The message resources for this package.
116: */
117: protected static MessageResources messages = MessageResources
118: .getMessageResources("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 = { ActionMapping.class, ActionForm.class,
148: HttpServletRequest.class, HttpServletResponse.class };
149:
150: // ----------------------------------------------------- Constructors
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: // --------------------------------------------------------- Public Methods
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,
192: ActionForm form, HttpServletRequest request,
193: HttpServletResponse response) throws Exception {
194: // Process "cancelled"
195: if (isCancelled(request)) {
196: ActionForward af = cancelled(mapping, form, request,
197: response);
198:
199: if (af != null) {
200: return af;
201: }
202: }
203:
204: // Identify the request parameter containing the method name
205: String parameter = getParameter(mapping, form, request,
206: response);
207:
208: // Get the method's name. This could be overridden in subclasses.
209: String name = getMethodName(mapping, form, request, response,
210: parameter);
211:
212: // Prevent recursive calls
213: if ("execute".equals(name) || "perform".equals(name)) {
214: String message = messages.getMessage("dispatch.recursive",
215: mapping.getPath());
216:
217: log.error(message);
218: throw new ServletException(message);
219: }
220:
221: // Invoke the named method, and return the result
222: return dispatchMethod(mapping, form, request, response, name);
223: }
224:
225: /**
226: * <p>Dispatches to the target class' <code>unspecified</code> method, if
227: * present, otherwise throws a ServletException. Classes utilizing
228: * <code>ActionDispatcher</code> should provide an <code>unspecified</code>
229: * method if they wish to provide behavior different than throwing a
230: * ServletException.</p>
231: *
232: * @param mapping The ActionMapping used to select this instance
233: * @param form The optional ActionForm bean for this request (if any)
234: * @param request The non-HTTP request we are processing
235: * @param response The non-HTTP response we are creating
236: * @return The forward to which control should be transferred, or
237: * <code>null</code> if the response has been completed.
238: * @throws Exception if the application business logic throws an
239: * exception.
240: */
241: protected ActionForward unspecified(ActionMapping mapping,
242: ActionForm form, HttpServletRequest request,
243: HttpServletResponse response) throws Exception {
244: // Identify if there is an "unspecified" method to be dispatched to
245: String name = "unspecified";
246: Method method = null;
247:
248: try {
249: method = getMethod(name);
250: } catch (NoSuchMethodException e) {
251: String message = messages.getMessage("dispatch.parameter",
252: mapping.getPath(), mapping.getParameter());
253:
254: log.error(message);
255:
256: throw new ServletException(message);
257: }
258:
259: return dispatchMethod(mapping, form, request, response, name,
260: method);
261: }
262:
263: /**
264: * <p>Dispatches to the target class' cancelled method, if present,
265: * otherwise returns null. Classes utilizing <code>ActionDispatcher</code>
266: * should provide a <code>cancelled</code> method if they wish to provide
267: * behavior different than returning null.</p>
268: *
269: * @param mapping The ActionMapping used to select this instance
270: * @param form The optional ActionForm bean for this request (if any)
271: * @param request The non-HTTP request we are processing
272: * @param response The non-HTTP response we are creating
273: * @return The forward to which control should be transferred, or
274: * <code>null</code> if the response has been completed.
275: * @throws Exception if the application business logic throws an
276: * exception.
277: */
278: protected ActionForward cancelled(ActionMapping mapping,
279: ActionForm form, HttpServletRequest request,
280: HttpServletResponse response) throws Exception {
281: // Identify if there is an "cancelled" method to be dispatched to
282: String name = "cancelled";
283: Method method = null;
284:
285: try {
286: method = getMethod(name);
287: } catch (NoSuchMethodException e) {
288: return null;
289: }
290:
291: return dispatchMethod(mapping, form, request, response, name,
292: method);
293: }
294:
295: // ----------------------------------------------------- Protected Methods
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) throws Exception {
313: // Make sure we have a valid method name to call.
314: // This may be null if the user hacks the query string.
315: if (name == null) {
316: return this .unspecified(mapping, form, request, response);
317: }
318:
319: // Identify the method object to be dispatched to
320: Method method = null;
321:
322: try {
323: method = getMethod(name);
324: } catch (NoSuchMethodException e) {
325: String message = messages.getMessage("dispatch.method",
326: mapping.getPath(), name);
327:
328: log.error(message, e);
329:
330: String userMsg = messages.getMessage(
331: "dispatch.method.user", mapping.getPath());
332: throw new NoSuchMethodException(userMsg);
333: }
334:
335: return dispatchMethod(mapping, form, request, response, name,
336: 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,
363: args);
364: } catch (ClassCastException e) {
365: String message = messages.getMessage("dispatch.return",
366: mapping.getPath(), name);
367:
368: log.error(message, e);
369: throw e;
370: } catch (IllegalAccessException e) {
371: String message = messages.getMessage("dispatch.error",
372: mapping.getPath(), name);
373:
374: log.error(message, e);
375: throw e;
376: } catch (InvocationTargetException e) {
377: // Rethrow the target exception if possible so that the
378: // exception handling machinery can deal with it
379: Throwable t = e.getTargetException();
380:
381: if (t instanceof Exception) {
382: throw ((Exception) t);
383: } else {
384: String message = messages.getMessage("dispatch.error",
385: mapping.getPath(), name);
386:
387: log.error(message, e);
388: throw new ServletException(t);
389: }
390: }
391:
392: // Return the returned ActionForward instance
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,
431: ActionForm form, HttpServletRequest request,
432: HttpServletResponse response) 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: // use "method" for DEFAULT_FLAVOR if no parameter was provided
441: return "method";
442: }
443:
444: if ((parameter == null)
445: && ((flavor == MAPPING_FLAVOR) || (flavor == DISPATCH_FLAVOR))) {
446: String message = messages.getMessage("dispatch.handler",
447: 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,
470: ActionForm form, HttpServletRequest request,
471: HttpServletResponse response, String parameter)
472: throws Exception {
473: // "Mapping" flavor, defaults to "method"
474: if (flavor == MAPPING_FLAVOR) {
475: return parameter;
476: }
477:
478: // default behaviour
479: return request.getParameter(parameter);
480: }
481:
482: /**
483: * <p>Returns <code>true</code> if the current form's cancel button was
484: * pressed. This method will check if the <code>Globals.CANCEL_KEY</code>
485: * request attribute has been set, which normally occurs if the cancel
486: * button generated by <strong>CancelTag</strong> was pressed by the user
487: * in the current request. If <code>true</code>, validation performed by
488: * an <strong>ActionForm</strong>'s <code>validate()</code> method will
489: * have been skipped by the controller servlet.</p>
490: *
491: * @param request The servlet request we are processing
492: * @return <code>true</code> if the current form's cancel button was
493: * pressed; <code>false</code> otherwise.
494: * @see org.apache.struts.taglib.html.CancelTag
495: */
496: protected boolean isCancelled(HttpServletRequest request) {
497: return (request.getAttribute(Globals.CANCEL_KEY) != null);
498: }
499: }
|