View Javadoc

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