1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
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
269 request = processMultipart(request);
270
271
272 ActionContext context = contextInstance(request, response);
273
274
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
283 throw new ServletException(e);
284 }
285
286
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 }