001: /*
002: * $Id: DispatchAction.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.action.ActionForm;
026: import org.apache.struts.action.ActionForward;
027: import org.apache.struts.action.ActionMapping;
028:
029: import javax.servlet.ServletException;
030: import javax.servlet.http.HttpServletRequest;
031: import javax.servlet.http.HttpServletResponse;
032:
033: import java.lang.reflect.InvocationTargetException;
034: import java.lang.reflect.Method;
035:
036: import java.util.HashMap;
037:
038: /**
039: * <p>An abstract <strong>Action</strong> that dispatches to a public method
040: * that is named by the request parameter whose name is specified by the
041: * <code>parameter</code> property of the corresponding ActionMapping. This
042: * Action is useful for developers who prefer to combine many similar actions
043: * into a single Action class, in order to simplify their application
044: * design.</p>
045: *
046: * <p>To configure the use of this action in your <code>struts-config.xml</code>
047: * file, create an entry like this:</p>
048: *
049: * <code> <action path="/saveSubscription" type="org.apache.struts.actions.DispatchAction"
050: * name="subscriptionForm" scope="request" input="/subscription.jsp"
051: * parameter="method"/> </code>
052: *
053: * <p>which will use the value of the request parameter named "method" to pick
054: * the appropriate "execute" method, which must have the same signature (other
055: * than method name) of the standard Action.execute method. For example, you
056: * might have the following three methods in the same action:</p>
057: *
058: * <ul>
059: *
060: * <li>public ActionForward delete(ActionMapping mapping, ActionForm form,
061: * HttpServletRequest request, HttpServletResponse response) throws
062: * Exception</li>
063: *
064: * <li>public ActionForward insert(ActionMapping mapping, ActionForm form,
065: * HttpServletRequest request, HttpServletResponse response) throws
066: * Exception</li>
067: *
068: * <li>public ActionForward update(ActionMapping mapping, ActionForm form,
069: * HttpServletRequest request, HttpServletResponse response) throws
070: * Exception</li>
071: *
072: * </ul>
073: *
074: * <p>and call one of the methods with a URL like this:</p>
075: *
076: * <p> <code> http://localhost:8080/myapp/saveSubscription.do?method=update
077: * </code></p>
078: *
079: * <p><strong>NOTE</strong> - All of the other mapping characteristics of this
080: * action must be shared by the various handlers. This places some
081: * constraints over what types of handlers may reasonably be packaged into the
082: * same <code>DispatchAction</code> subclass.</p>
083: *
084: * <p><strong>NOTE</strong> - If the value of the request parameter is empty,
085: * a method named <code>unspecified</code> is called. The default action is to
086: * throw an exception. If the request was cancelled (a
087: * <code>html:cancel</code> button was pressed), the custom handler
088: * <code>cancelled</code> will be used instead. You can also override the
089: * <code>getMethodName</code> method to override the action's default handler
090: * selection.</p>
091: *
092: * @version $Rev: 471754 $ $Date: 2006-11-06 08:55:09 -0600 (Mon, 06 Nov 2006) $
093: */
094: public abstract class DispatchAction extends BaseAction {
095: /**
096: * Commons Logging instance.
097: */
098: protected static Log log = LogFactory.getLog(DispatchAction.class);
099:
100: // ----------------------------------------------------- Instance Variables
101:
102: /**
103: * The Class instance of this <code>DispatchAction</code> class.
104: */
105: protected Class clazz = this .getClass();
106:
107: /**
108: * The set of Method objects we have introspected for this class, keyed by
109: * method name. This collection is populated as different methods are
110: * called, so that introspection needs to occur only once per method
111: * name.
112: */
113: protected HashMap methods = new HashMap();
114:
115: /**
116: * The set of argument type classes for the reflected method call. These
117: * are the same for all calls, so calculate them only once.
118: */
119: protected Class[] types = { ActionMapping.class, ActionForm.class,
120: HttpServletRequest.class, HttpServletResponse.class };
121:
122: // --------------------------------------------------------- Public Methods
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,
140: ActionForm form, HttpServletRequest request,
141: HttpServletResponse response) throws Exception {
142: if (isCancelled(request)) {
143: ActionForward af = cancelled(mapping, form, request,
144: response);
145:
146: if (af != null) {
147: return af;
148: }
149: }
150:
151: // Get the parameter. This could be overridden in subclasses.
152: String parameter = getParameter(mapping, form, request,
153: response);
154:
155: // Get the method's name. This could be overridden in subclasses.
156: String name = getMethodName(mapping, form, request, response,
157: parameter);
158:
159: // Prevent recursive calls
160: if ("execute".equals(name) || "perform".equals(name)) {
161: String message = messages.getMessage("dispatch.recursive",
162: mapping.getPath());
163:
164: log.error(message);
165: throw new ServletException(message);
166: }
167:
168: // Invoke the named method, and return the result
169: return dispatchMethod(mapping, form, request, response, name);
170: }
171:
172: /**
173: * Method which is dispatched to when there is no value for specified
174: * request parameter included in the request. Subclasses of
175: * <code>DispatchAction</code> should override this method if they wish to
176: * provide default behavior different than throwing a ServletException.
177: *
178: * @param mapping The ActionMapping used to select this instance
179: * @param form The optional ActionForm bean for this request (if any)
180: * @param request The non-HTTP request we are processing
181: * @param response The non-HTTP response we are creating
182: * @return The forward to which control should be transferred, or
183: * <code>null</code> if the response has been completed.
184: * @throws Exception if the application business logic throws an
185: * exception.
186: */
187: protected ActionForward unspecified(ActionMapping mapping,
188: ActionForm form, HttpServletRequest request,
189: HttpServletResponse response) throws Exception {
190: String message = messages.getMessage("dispatch.parameter",
191: mapping.getPath(), mapping.getParameter());
192:
193: log.error(message);
194:
195: throw new ServletException(message);
196: }
197:
198: /**
199: * Method which is dispatched to when the request is a cancel button
200: * submit. Subclasses of <code>DispatchAction</code> should override this
201: * method if they wish to provide default behavior different than
202: * returning null.
203: *
204: * @param mapping The ActionMapping used to select this instance
205: * @param form The optional ActionForm bean for this request (if any)
206: * @param request The non-HTTP request we are processing
207: * @param response The non-HTTP response we are creating
208: * @return The forward to which control should be transferred, or
209: * <code>null</code> if the response has been completed.
210: * @throws Exception if the application business logic throws an
211: * exception.
212: * @since Struts 1.2.0
213: */
214: protected ActionForward cancelled(ActionMapping mapping,
215: ActionForm form, HttpServletRequest request,
216: HttpServletResponse response) throws Exception {
217: return null;
218: }
219:
220: // ----------------------------------------------------- Protected Methods
221:
222: /**
223: * Dispatch to the specified method.
224: *
225: * @param mapping The ActionMapping used to select this instance
226: * @param form The optional ActionForm bean for this request (if any)
227: * @param request The non-HTTP request we are processing
228: * @param response The non-HTTP response we are creating
229: * @param name The name of the method to invoke
230: * @return The forward to which control should be transferred, or
231: * <code>null</code> if the response has been completed.
232: * @throws Exception if the application business logic throws an
233: * exception.
234: * @since Struts 1.1
235: */
236: protected ActionForward dispatchMethod(ActionMapping mapping,
237: ActionForm form, HttpServletRequest request,
238: HttpServletResponse response, String name) throws Exception {
239: // Make sure we have a valid method name to call.
240: // This may be null if the user hacks the query string.
241: if (name == null) {
242: return this .unspecified(mapping, form, request, response);
243: }
244:
245: // Identify the method object to be dispatched to
246: Method method = null;
247:
248: try {
249: method = getMethod(name);
250: } catch (NoSuchMethodException e) {
251: String message = messages.getMessage("dispatch.method",
252: mapping.getPath(), name);
253:
254: log.error(message, e);
255:
256: String userMsg = messages.getMessage(
257: "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 = messages.getMessage("dispatch.return",
269: mapping.getPath(), name);
270:
271: log.error(message, e);
272: throw e;
273: } catch (IllegalAccessException e) {
274: String message = messages.getMessage("dispatch.error",
275: mapping.getPath(), name);
276:
277: log.error(message, e);
278: throw e;
279: } catch (InvocationTargetException e) {
280: // Rethrow the target exception if possible so that the
281: // exception handling machinery can deal with it
282: Throwable t = e.getTargetException();
283:
284: if (t instanceof Exception) {
285: throw ((Exception) t);
286: } else {
287: String message = messages.getMessage("dispatch.error",
288: mapping.getPath(), name);
289:
290: log.error(message, e);
291: throw new ServletException(t);
292: }
293: }
294:
295: // Return the returned ActionForward instance
296: return (forward);
297: }
298:
299: /**
300: * <p>Returns the parameter value.</p>
301: *
302: * @param mapping The ActionMapping used to select this instance
303: * @param form The optional ActionForm bean for this request (if any)
304: * @param request The HTTP request we are processing
305: * @param response The HTTP response we are creating
306: * @return The <code>ActionMapping</code> parameter's value
307: * @throws Exception if the parameter is missing.
308: */
309: protected String getParameter(ActionMapping mapping,
310: ActionForm form, HttpServletRequest request,
311: HttpServletResponse response) throws Exception {
312:
313: // Identify the request parameter containing the method name
314: String parameter = mapping.getParameter();
315:
316: if (parameter == null) {
317: String message = messages.getMessage("dispatch.handler",
318: mapping.getPath());
319:
320: log.error(message);
321:
322: throw new ServletException(message);
323: }
324:
325: return parameter;
326: }
327:
328: /**
329: * Introspect the current class to identify a method of the specified name
330: * that accepts the same parameter types as the <code>execute</code>
331: * method does.
332: *
333: * @param name Name of the method to be introspected
334: * @return The method with the specified name.
335: * @throws NoSuchMethodException if no such method can be found
336: */
337: protected Method getMethod(String name)
338: throws NoSuchMethodException {
339: synchronized (methods) {
340: Method method = (Method) methods.get(name);
341:
342: if (method == null) {
343: method = clazz.getMethod(name, types);
344: methods.put(name, method);
345: }
346:
347: return (method);
348: }
349: }
350:
351: /**
352: * Returns the method name, given a parameter's value.
353: *
354: * @param mapping The ActionMapping used to select this instance
355: * @param form The optional ActionForm bean for this request (if
356: * any)
357: * @param request The HTTP request we are processing
358: * @param response The HTTP response we are creating
359: * @param parameter The <code>ActionMapping</code> parameter's name
360: * @return The method's name.
361: * @throws Exception if an error occurs.
362: * @since Struts 1.2.0
363: */
364: protected String getMethodName(ActionMapping mapping,
365: ActionForm form, HttpServletRequest request,
366: HttpServletResponse response, String parameter)
367: throws Exception {
368: // Identify the method name to be dispatched to.
369: // dispatchMethod() will call unspecified() if name is null
370: return request.getParameter(parameter);
371: }
372: }
|