001: /*
002: * $Id: ComposableRequestProcessor.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.chain;
022:
023: import org.apache.commons.beanutils.ConstructorUtils;
024: import org.apache.commons.chain.Catalog;
025: import org.apache.commons.chain.CatalogFactory;
026: import org.apache.commons.chain.Command;
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029: import org.apache.struts.action.ActionServlet;
030: import org.apache.struts.action.RequestProcessor;
031: import org.apache.struts.chain.contexts.ActionContext;
032: import org.apache.struts.chain.contexts.ServletActionContext;
033: import org.apache.struts.config.ControllerConfig;
034: import org.apache.struts.config.ModuleConfig;
035: import org.apache.struts.upload.MultipartRequestWrapper;
036: import org.apache.struts.util.RequestUtils;
037:
038: import javax.servlet.ServletContext;
039: import javax.servlet.ServletException;
040: import javax.servlet.UnavailableException;
041: import javax.servlet.http.HttpServletRequest;
042: import javax.servlet.http.HttpServletResponse;
043:
044: import java.io.IOException;
045:
046: import java.lang.reflect.Constructor;
047:
048: /**
049: * <p> ComposableRequestProcessor uses the Chain Of Resposibility design
050: * pattern (as implemented by the commons-chain package in Jakarta Commons) to
051: * support external configuration of command chains to be used. It is
052: * configured via the following context initialization parameters: </p>
053: *
054: * <ul>
055: *
056: * <li>[org.apache.struts.chain.CATALOG_NAME] - Name of the Catalog in which
057: * we will look up the Command to be executed for each request. If not
058: * specified, the default value is struts. </li>
059: *
060: * <li> org.apache.struts.chain.COMMAND_NAME - Name of the Command which we
061: * will execute for each request, to be looked up in the specified Catalog.
062: * If not specified, the default value is servlet-standard. </li>
063: *
064: * </ul>
065: *
066: * @version $Rev: 471754 $ $Date: 2005-11-12 13:01:44 -0500 (Sat, 12 Nov 2005)
067: * $
068: * @since Struts 1.1
069: */
070: public class ComposableRequestProcessor extends RequestProcessor {
071: // ------------------------------------------------------ Instance Variables
072:
073: /**
074: * <p> Cache for constructor discovered by setActionContextClass method.
075: * </p>
076: */
077: private static final Class[] SERVLET_ACTION_CONTEXT_CTOR_SIGNATURE = new Class[] {
078: ServletContext.class, HttpServletRequest.class,
079: HttpServletResponse.class };
080:
081: /**
082: * <p> Token for ActionContext clazss so that it can be stored in the
083: * ControllerConfig. </p>
084: */
085: public static final String ACTION_CONTEXT_CLASS = "ACTION_CONTEXT_CLASS";
086:
087: /**
088: * <p>The <code>Log</code> instance for this class.</p>
089: */
090: protected static final Log LOG = LogFactory
091: .getLog(ComposableRequestProcessor.class);
092:
093: /**
094: * <p>The {@link CatalogFactory} from which catalog containing the the
095: * base request-processing {@link Command} will be retrieved.</p>
096: */
097: protected CatalogFactory catalogFactory = null;
098:
099: /**
100: * <p>The {@link Catalog} containing all of the available command chains
101: * for this module.
102: */
103: protected Catalog catalog = null;
104:
105: /**
106: * <p>The {@link Command} to be executed for each request.</p>
107: */
108: protected Command command = null;
109:
110: /**
111: * <p> ActionContext class as cached by createActionContextInstance
112: * method. </p>
113: */
114: private Class actionContextClass;
115:
116: /**
117: * <p> ActionContext constructor as cached by createActionContextInstance
118: * method. </p>
119: */
120: private Constructor servletActionContextConstructor = null;
121:
122: // ---------------------------------------------------------- Public Methods
123:
124: /**
125: * <p>Clean up in preparation for a shutdown of this application.</p>
126: */
127: public void destroy() {
128: super .destroy();
129: catalogFactory = null;
130: catalog = null;
131: command = null;
132: actionContextClass = null;
133: servletActionContextConstructor = null;
134: }
135:
136: /**
137: * <p>Initialize this request processor instance.</p>
138: *
139: * @param servlet The ActionServlet we are associated with
140: * @param moduleConfig The ModuleConfig we are associated with.
141: * @throws ServletException If an error occurs during initialization
142: */
143: public void init(ActionServlet servlet, ModuleConfig moduleConfig)
144: throws ServletException {
145: LOG
146: .info("Initializing composable request processor for module prefix '"
147: + moduleConfig.getPrefix() + "'");
148: super .init(servlet, moduleConfig);
149:
150: initCatalogFactory(servlet, moduleConfig);
151:
152: ControllerConfig controllerConfig = moduleConfig
153: .getControllerConfig();
154:
155: String catalogName = controllerConfig.getCatalog();
156:
157: catalog = this .catalogFactory.getCatalog(catalogName);
158:
159: if (catalog == null) {
160: throw new ServletException("Cannot find catalog '"
161: + catalogName + "'");
162: }
163:
164: String commandName = controllerConfig.getCommand();
165:
166: command = catalog.getCommand(commandName);
167:
168: if (command == null) {
169: throw new ServletException("Cannot find command '"
170: + commandName + "'");
171: }
172:
173: this .setActionContextClassName(controllerConfig
174: .getProperty(ACTION_CONTEXT_CLASS));
175: }
176:
177: /**
178: * <p> Set and cache ActionContext class. </p><p> If there is a custom
179: * class provided and if it uses our "preferred" constructor, cache a
180: * reference to that constructor rather than looking it up every time.
181: * </p>
182: *
183: * @param actionContextClass The ActionContext class to process
184: */
185: private void setActionContextClass(Class actionContextClass) {
186: this .actionContextClass = actionContextClass;
187:
188: if (actionContextClass != null) {
189: this .servletActionContextConstructor = ConstructorUtils
190: .getAccessibleConstructor(actionContextClass,
191: SERVLET_ACTION_CONTEXT_CTOR_SIGNATURE);
192: } else {
193: this .servletActionContextConstructor = null;
194: }
195: }
196:
197: /**
198: * <p>Make sure that the specified <code>className</code> identfies a
199: * class which can be found and which implements the
200: * <code>ActionContext</code> interface.</p>
201: *
202: * @param className Fully qualified name of
203: * @throws ServletException If an error occurs during initialization
204: * @throws UnavailableException if class does not implement ActionContext
205: * or is not found
206: */
207: private void setActionContextClassName(String className)
208: throws ServletException {
209: if ((className != null) && (className.trim().length() > 0)) {
210: if (LOG.isDebugEnabled()) {
211: LOG
212: .debug("setActionContextClassName: requested context class: "
213: + className);
214: }
215:
216: try {
217: Class actionContextClass = RequestUtils
218: .applicationClass(className);
219:
220: if (!ActionContext.class
221: .isAssignableFrom(actionContextClass)) {
222: throw new UnavailableException(
223: "ActionContextClass "
224: + "["
225: + className
226: + "]"
227: + " must implement ActionContext interface.");
228: }
229:
230: this .setActionContextClass(actionContextClass);
231: } catch (ClassNotFoundException e) {
232: throw new UnavailableException("ActionContextClass "
233: + className + " not found.");
234: }
235: } else {
236: if (LOG.isDebugEnabled()) {
237: LOG
238: .debug("setActionContextClassName: no className specified");
239: }
240:
241: this .setActionContextClass(null);
242: }
243: }
244:
245: /**
246: * <p> Establish the CatalogFactory which will be used to look up the
247: * catalog which has the request processing command. </p><p> The base
248: * implementation simply calls CatalogFactory.getInstance(), unless the
249: * catalogFactory property of this object has already been set, in which
250: * case it is not changed. </p>
251: *
252: * @param servlet The ActionServlet we are processing
253: * @param moduleConfig The ModuleConfig we are processing
254: */
255: protected void initCatalogFactory(ActionServlet servlet,
256: ModuleConfig moduleConfig) {
257: if (this .catalogFactory != null) {
258: return;
259: }
260:
261: this .catalogFactory = CatalogFactory.getInstance();
262: }
263:
264: /**
265: * <p>Process an <code>HttpServletRequest</code> and create the
266: * corresponding <code>HttpServletResponse</code>.</p>
267: *
268: * @param request The servlet request we are processing
269: * @param response The servlet response we are creating
270: * @throws IOException if an input/output error occurs
271: * @throws ServletException if a processing exception occurs
272: */
273: public void process(HttpServletRequest request,
274: HttpServletResponse response) throws IOException,
275: ServletException {
276: // Wrap the request in the case of a multipart request
277: request = processMultipart(request);
278:
279: // Create and populate a Context for this request
280: ActionContext context = contextInstance(request, response);
281:
282: // Create and execute the command.
283: try {
284: if (LOG.isDebugEnabled()) {
285: LOG.debug("Using processing chain for this request");
286: }
287:
288: command.execute(context);
289: } catch (Exception e) {
290: // Execute the exception processing chain??
291: throw new ServletException(e);
292: }
293:
294: // Release the context.
295: context.release();
296: }
297:
298: /**
299: * <p>Provide the initialized <code>ActionContext</code> instance which
300: * will be used by this request. Internally, this simply calls
301: * <code>createActionContextInstance</code> followed by
302: * <code>initializeActionContext</code>.</p>
303: *
304: * @param request The servlet request we are processing
305: * @param response The servlet response we are creating
306: * @return Initiliazed ActionContext
307: * @throws ServletException if a processing exception occurs
308: */
309: protected ActionContext contextInstance(HttpServletRequest request,
310: HttpServletResponse response) throws ServletException {
311: ActionContext context = createActionContextInstance(
312: getServletContext(), request, response);
313:
314: initializeActionContext(context);
315:
316: return context;
317: }
318:
319: /**
320: * <p>Create a new instance of <code>ActionContext</code> according to
321: * configuration. If no alternative was specified at initialization, a
322: * new instance <code>ServletActionContext</code> is returned. If an
323: * alternative was specified using the <code>ACTION_CONTEXT_CLASS</code>
324: * property, then that value is treated as a classname, and an instance of
325: * that class is created. If that class implements the same constructor
326: * that <code>ServletActionContext</code> does, then that constructor will
327: * be used: <code>ServletContext, HttpServletRequest,
328: * HttpServletResponse</code>; otherwise, it is assumed that the class has
329: * a no-arguments constructor. If these constraints do not suit you,
330: * simply override this method in a subclass.</p>
331: *
332: * @param servletContext The servlet context we are processing
333: * @param request The servlet request we are processing
334: * @param response The servlet response we are creating
335: * @return New instance of ActionContext
336: * @throws ServletException if a processing exception occurs
337: */
338: protected ActionContext createActionContextInstance(
339: ServletContext servletContext, HttpServletRequest request,
340: HttpServletResponse response) throws ServletException {
341: if (this .actionContextClass == null) {
342: return new ServletActionContext(servletContext, request,
343: response);
344: }
345:
346: try {
347: if (this .servletActionContextConstructor == null) {
348: return (ActionContext) this .actionContextClass
349: .newInstance();
350: }
351:
352: return (ActionContext) this .servletActionContextConstructor
353: .newInstance(new Object[] { servletContext,
354: request, response });
355: } catch (Exception e) {
356: throw new ServletException(
357: "Error creating ActionContext instance of type "
358: + this .actionContextClass, e);
359: }
360: }
361:
362: /**
363: * <p>Set common properties on the given <code>ActionContext</code>
364: * instance so that commands in the chain can count on their presence.
365: * Note that while this method does not require that its argument be an
366: * instance of <code>ServletActionContext</code>, at this time many common
367: * Struts commands will be expecting to receive an <code>ActionContext</code>
368: * which is also a <code>ServletActionContext</code>.</p>
369: *
370: * @param context The ActionContext we are processing
371: */
372: protected void initializeActionContext(ActionContext context) {
373: if (context instanceof ServletActionContext) {
374: ((ServletActionContext) context)
375: .setActionServlet(this .servlet);
376: }
377:
378: context.setModuleConfig(this .moduleConfig);
379: }
380:
381: /**
382: * <p>If this is a multipart request, wrap it with a special wrapper.
383: * Otherwise, return the request unchanged.</p>
384: *
385: * @param request The HttpServletRequest we are processing
386: * @return Original or wrapped request as appropriate
387: */
388: protected HttpServletRequest processMultipart(
389: HttpServletRequest request) {
390: if (!"POST".equalsIgnoreCase(request.getMethod())) {
391: return (request);
392: }
393:
394: String contentType = request.getContentType();
395:
396: if ((contentType != null)
397: && contentType.startsWith("multipart/form-data")) {
398: return (new MultipartRequestWrapper(request));
399: } else {
400: return (request);
401: }
402: }
403:
404: /**
405: * <p>Set the <code>CatalogFactory</code> instance which should be used to
406: * find the request-processing command. In the base implementation, if
407: * this value is not already set, then it will be initialized when {@link
408: * #initCatalogFactory} is called. </p>
409: *
410: * @param catalogFactory Our CatalogFactory instance
411: */
412: public void setCatalogFactory(CatalogFactory catalogFactory) {
413: this.catalogFactory = catalogFactory;
414: }
415: }
|