View Javadoc

1   /*
2    * $Id: ComposableRequestProcessor.java 421119 2006-07-12 04:49:11Z wsmoak $
3    *
4    * Copyright 2003-2005 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  package org.apache.struts.chain;
19  
20  import org.apache.commons.beanutils.ConstructorUtils;
21  import org.apache.commons.chain.Catalog;
22  import org.apache.commons.chain.CatalogFactory;
23  import org.apache.commons.chain.Command;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.struts.action.ActionServlet;
27  import org.apache.struts.action.RequestProcessor;
28  import org.apache.struts.chain.contexts.ActionContext;
29  import org.apache.struts.chain.contexts.ServletActionContext;
30  import org.apache.struts.config.ControllerConfig;
31  import org.apache.struts.config.ModuleConfig;
32  import org.apache.struts.upload.MultipartRequestWrapper;
33  import org.apache.struts.util.RequestUtils;
34  
35  import javax.servlet.ServletContext;
36  import javax.servlet.ServletException;
37  import javax.servlet.UnavailableException;
38  import javax.servlet.http.HttpServletRequest;
39  import javax.servlet.http.HttpServletResponse;
40  
41  import java.io.IOException;
42  
43  import java.lang.reflect.Constructor;
44  
45  /***
46   * <p> ComposableRequestProcessor uses the Chain Of Resposibility design
47   * pattern (as implemented by the commons-chain package in Jakarta Commons) to
48   * support external configuration of command chains to be used.  It is
49   * configured via the following context initialization parameters: </p>
50   *
51   * <ul>
52   *
53   * <li>[org.apache.struts.chain.CATALOG_NAME] - Name of the Catalog in which
54   * we will look up the Command to be executed for each request.  If not
55   * specified, the default value is struts. </li>
56   *
57   * <li> org.apache.struts.chain.COMMAND_NAME - Name of the Command which we
58   * will execute for each request, to be looked up in the specified Catalog.
59   * If not specified, the default value is servlet-standard. </li>
60   *
61   * </ul>
62   *
63   * @version $Rev: 421119 $ $Date: 2005-11-12 13:01:44 -0500 (Sat, 12 Nov 2005)
64   *          $
65   * @since Struts 1.1
66   */
67  public class ComposableRequestProcessor extends RequestProcessor {
68      // ------------------------------------------------------ Instance Variables
69  
70      /***
71       * <p> Cache for constructor discovered by setActionContextClass method.
72       * </p>
73       */
74      private static final Class[] SERVLET_ACTION_CONTEXT_CTOR_SIGNATURE =
75          new Class[] {
76              ServletContext.class, HttpServletRequest.class,
77              HttpServletResponse.class
78          };
79  
80      /***
81       * <p> Token for ActionContext clazss so that it can be stored in the
82       * ControllerConfig. </p>
83       */
84      public static final String ACTION_CONTEXT_CLASS = "ACTION_CONTEXT_CLASS";
85  
86      /***
87       * <p>The <code>Log</code> instance for this class.</p>
88       */
89      protected static final Log LOG =
90          LogFactory.getLog(ComposableRequestProcessor.class);
91  
92      /***
93       * <p>The {@link CatalogFactory} from which catalog containing the the
94       * base request-processing {@link Command} will be retrieved.</p>
95       */
96      protected CatalogFactory catalogFactory = null;
97  
98      /***
99       * <p>The {@link Catalog} containing all of the available command chains
100      * for this module.
101      */
102     protected Catalog catalog = null;
103 
104     /***
105      * <p>The {@link Command} to be executed for each request.</p>
106      */
107     protected Command command = null;
108 
109     /***
110      * <p> ActionContext class as cached by createActionContextInstance
111      * method. </p>
112      */
113     private Class actionContextClass;
114 
115     /***
116      * <p> ActionContext constructor as cached by createActionContextInstance
117      * method. </p>
118      */
119     private Constructor servletActionContextConstructor = null;
120 
121     // ---------------------------------------------------------- Public Methods
122 
123     /***
124      * <p>Clean up in preparation for a shutdown of this application.</p>
125      */
126     public void destroy() {
127         super.destroy();
128         catalogFactory = null;
129         catalog = null;
130         command = null;
131         actionContextClass = null;
132         servletActionContextConstructor = null;
133     }
134 
135     /***
136      * <p>Initialize this request processor instance.</p>
137      *
138      * @param servlet      The ActionServlet we are associated with
139      * @param moduleConfig The ModuleConfig we are associated with.
140      * @throws ServletException If an error occurs during initialization
141      */
142     public void init(ActionServlet servlet, ModuleConfig moduleConfig)
143         throws ServletException {
144         LOG.info(
145             "Initializing composable request processor for module prefix '"
146             + moduleConfig.getPrefix() + "'");
147         super.init(servlet, moduleConfig);
148 
149         initCatalogFactory(servlet, moduleConfig);
150 
151         ControllerConfig controllerConfig = moduleConfig.getControllerConfig();
152 
153         String catalogName = controllerConfig.getCatalog();
154 
155         catalog = this.catalogFactory.getCatalog(catalogName);
156 
157         if (catalog == null) {
158             throw new ServletException("Cannot find catalog '" + catalogName
159                 + "'");
160         }
161 
162         String commandName = controllerConfig.getCommand();
163 
164         command = catalog.getCommand(commandName);
165 
166         if (command == null) {
167             throw new ServletException("Cannot find command '" + commandName
168                 + "'");
169         }
170 
171         this.setActionContextClassName(controllerConfig.getProperty(
172                 ACTION_CONTEXT_CLASS));
173     }
174 
175     /***
176      * <p> Set and cache ActionContext class. </p><p> If there is a custom
177      * class provided and if it uses our "preferred" constructor, cache a
178      * reference to that constructor rather than looking it up every time.
179      * </p>
180      *
181      * @param actionContextClass The ActionContext class to process
182      */
183     private void setActionContextClass(Class actionContextClass) {
184         this.actionContextClass = actionContextClass;
185 
186         if (actionContextClass != null) {
187             this.servletActionContextConstructor =
188                 ConstructorUtils.getAccessibleConstructor(actionContextClass,
189                     SERVLET_ACTION_CONTEXT_CTOR_SIGNATURE);
190         } else {
191             this.servletActionContextConstructor = null;
192         }
193     }
194 
195     /***
196      * <p>Make sure that the specified <code>className</code> identfies a
197      * class which can be found and which implements the
198      * <code>ActionContext</code> interface.</p>
199      *
200      * @param className Fully qualified name of
201      * @throws ServletException     If an error occurs during initialization
202      * @throws UnavailableException if class does not implement ActionContext
203      *                              or is not found
204      */
205     private void setActionContextClassName(String className)
206         throws ServletException {
207         if ((className != null) && (className.trim().length() > 0)) {
208             if (LOG.isDebugEnabled()) {
209                 LOG.debug(
210                     "setActionContextClassName: requested context class: "
211                     + className);
212             }
213 
214             try {
215                 Class actionContextClass =
216                     RequestUtils.applicationClass(className);
217 
218                 if (!ActionContext.class.isAssignableFrom(actionContextClass)) {
219                     throw new UnavailableException("ActionContextClass " + "["
220                         + className + "]"
221                         + " must implement ActionContext interface.");
222                 }
223 
224                 this.setActionContextClass(actionContextClass);
225             } catch (ClassNotFoundException e) {
226                 throw new UnavailableException("ActionContextClass "
227                     + className + " not found.");
228             }
229         } else {
230             if (LOG.isDebugEnabled()) {
231                 LOG.debug("setActionContextClassName: no className specified");
232             }
233 
234             this.setActionContextClass(null);
235         }
236     }
237 
238     /***
239      * <p> Establish the CatalogFactory which will be used to look up the
240      * catalog which has the request processing command. </p><p> The base
241      * implementation simply calls CatalogFactory.getInstance(), unless the
242      * catalogFactory property of this object has already been set, in which
243      * case it is not changed. </p>
244      *
245      * @param servlet      The ActionServlet we are processing
246      * @param moduleConfig The ModuleConfig we are processing
247      */
248     protected void initCatalogFactory(ActionServlet servlet,
249         ModuleConfig moduleConfig) {
250         if (this.catalogFactory != null) {
251             return;
252         }
253 
254         this.catalogFactory = CatalogFactory.getInstance();
255     }
256 
257     /***
258      * <p>Process an <code>HttpServletRequest</code> and create the
259      * corresponding <code>HttpServletResponse</code>.</p>
260      *
261      * @param request  The servlet request we are processing
262      * @param response The servlet response we are creating
263      * @throws IOException      if an input/output error occurs
264      * @throws ServletException if a processing exception occurs
265      */
266     public void process(HttpServletRequest request, HttpServletResponse response)
267         throws IOException, ServletException {
268         // Wrap the request in the case of a multipart request
269         request = processMultipart(request);
270 
271         // Create and populate a Context for this request
272         ActionContext context = contextInstance(request, response);
273 
274         // Create and execute the command.
275         try {
276             if (LOG.isDebugEnabled()) {
277                 LOG.debug("Using processing chain for this request");
278             }
279 
280             command.execute(context);
281         } catch (Exception e) {
282             // Execute the exception processing chain??
283             throw new ServletException(e);
284         }
285 
286         // Release the context.
287         context.release();
288     }
289 
290     /***
291      * <p>Provide the initialized <code>ActionContext</code> instance which
292      * will be used by this request. Internally, this simply calls
293      * <code>createActionContextInstance</code> followed by
294      * <code>initializeActionContext</code>.</p>
295      *
296      * @param request  The servlet request we are processing
297      * @param response The servlet response we are creating
298      * @return Initiliazed ActionContext
299      * @throws ServletException if a processing exception occurs
300      */
301     protected ActionContext contextInstance(HttpServletRequest request,
302         HttpServletResponse response)
303         throws ServletException {
304         ActionContext context =
305             createActionContextInstance(getServletContext(), request, response);
306 
307         initializeActionContext(context);
308 
309         return context;
310     }
311 
312     /***
313      * <p>Create a new instance of <code>ActionContext</code> according to
314      * configuration.  If no alternative was specified at initialization, a
315      * new instance <code>ServletActionContext</code> is returned.  If an
316      * alternative was specified using the <code>ACTION_CONTEXT_CLASS</code>
317      * property, then that value is treated as a classname, and an instance of
318      * that class is created.  If that class implements the same constructor
319      * that <code>ServletActionContext</code> does, then that constructor will
320      * be used: <code>ServletContext, HttpServletRequest,
321      * HttpServletResponse</code>; otherwise, it is assumed that the class has
322      * a no-arguments constructor.  If these constraints do not suit you,
323      * simply override this method in a subclass.</p>
324      *
325      * @param servletContext The servlet context we are processing
326      * @param request        The servlet request we are processing
327      * @param response       The servlet response we are creating
328      * @return New instance of ActionContext
329      * @throws ServletException if a processing exception occurs
330      */
331     protected ActionContext createActionContextInstance(
332         ServletContext servletContext, HttpServletRequest request,
333         HttpServletResponse response)
334         throws ServletException {
335         if (this.actionContextClass == null) {
336             return new ServletActionContext(servletContext, request, response);
337         }
338 
339         try {
340             if (this.servletActionContextConstructor == null) {
341                 return (ActionContext) this.actionContextClass.newInstance();
342             }
343 
344             return (ActionContext) this.servletActionContextConstructor
345             .newInstance(new Object[] { servletContext, request, response });
346         } catch (Exception e) {
347             throw new ServletException(
348                 "Error creating ActionContext instance of type "
349                 + this.actionContextClass, e);
350         }
351     }
352 
353     /***
354      * <p>Set common properties on the given <code>ActionContext</code>
355      * instance so that commands in the chain can count on their presence.
356      * Note that while this method does not require that its argument be an
357      * instance of <code>ServletActionContext</code>, at this time many common
358      * Struts commands will be expecting to receive an <code>ActionContext</code>
359      * which is also a <code>ServletActionContext</code>.</p>
360      *
361      * @param context The ActionContext we are processing
362      */
363     protected void initializeActionContext(ActionContext context) {
364         if (context instanceof ServletActionContext) {
365             ((ServletActionContext) context).setActionServlet(this.servlet);
366         }
367 
368         context.setModuleConfig(this.moduleConfig);
369     }
370 
371     /***
372      * <p>If this is a multipart request, wrap it with a special wrapper.
373      * Otherwise, return the request unchanged.</p>
374      *
375      * @param request The HttpServletRequest we are processing
376      * @return Original or wrapped request as appropriate
377      */
378     protected HttpServletRequest processMultipart(HttpServletRequest request) {
379         if (!"POST".equalsIgnoreCase(request.getMethod())) {
380             return (request);
381         }
382 
383         String contentType = request.getContentType();
384 
385         if ((contentType != null)
386             && contentType.startsWith("multipart/form-data")) {
387             return (new MultipartRequestWrapper(request));
388         } else {
389             return (request);
390         }
391     }
392 
393     /***
394      * <p>Set the <code>CatalogFactory</code> instance which should be used to
395      * find the request-processing command.  In the base implementation, if
396      * this value is not already set, then it will be initialized when {@link
397      * #initCatalogFactory} is called. </p>
398      *
399      * @param catalogFactory Our CatalogFactory instance
400      */
401     public void setCatalogFactory(CatalogFactory catalogFactory) {
402         this.catalogFactory = catalogFactory;
403     }
404 }