/* * $Id: ComposableRequestProcessor.java 471754 2006-11-06 14:55:09Z husted $ * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.struts.chain; import org.apache.commons.beanutils.ConstructorUtils; import org.apache.commons.chain.Catalog; import org.apache.commons.chain.CatalogFactory; import org.apache.commons.chain.Command; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.action.ActionServlet; import org.apache.struts.action.RequestProcessor; import org.apache.struts.chain.contexts.ActionContext; import org.apache.struts.chain.contexts.ServletActionContext; import org.apache.struts.config.ControllerConfig; import org.apache.struts.config.ModuleConfig; import org.apache.struts.upload.MultipartRequestWrapper; import org.apache.struts.util.RequestUtils; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.UnavailableException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Constructor; /** * <p> ComposableRequestProcessor uses the Chain Of Resposibility design * pattern (as implemented by the commons-chain package in Jakarta Commons) to * support external configuration of command chains to be used. It is * configured via the following context initialization parameters: </p> * * <ul> * * <li>[org.apache.struts.chain.CATALOG_NAME] - Name of the Catalog in which * we will look up the Command to be executed for each request. If not * specified, the default value is struts. </li> * * <li> org.apache.struts.chain.COMMAND_NAME - Name of the Command which we * will execute for each request, to be looked up in the specified Catalog. * If not specified, the default value is servlet-standard. </li> * * </ul> * * @version $Rev: 471754 $ $Date: 2005-11-12 13:01:44 -0500 (Sat, 12 Nov 2005) * $ * @since Struts 1.1 */ public class ComposableRequestProcessor extends RequestProcessor { // ------------------------------------------------------ Instance Variables /** * <p> Cache for constructor discovered by setActionContextClass method. * </p> */ private static final Class[] SERVLET_ACTION_CONTEXT_CTOR_SIGNATURE = new Class[] { ServletContext.class, HttpServletRequest.class, HttpServletResponse.class }; /** * <p> Token for ActionContext clazss so that it can be stored in the * ControllerConfig. </p> */ public static final String ACTION_CONTEXT_CLASS = "ACTION_CONTEXT_CLASS"; /** * <p>The <code>Log</code> instance for this class.</p> */ protected static final Log LOG = LogFactory.getLog(ComposableRequestProcessor.class); /** * <p>The {@link CatalogFactory} from which catalog containing the the * base request-processing {@link Command} will be retrieved.</p> */ protected CatalogFactory catalogFactory = null; /** * <p>The {@link Catalog} containing all of the available command chains * for this module. */ protected Catalog catalog = null; /** * <p>The {@link Command} to be executed for each request.</p> */ protected Command command = null; /** * <p> ActionContext class as cached by createActionContextInstance * method. </p> */ private Class actionContextClass; /** * <p> ActionContext constructor as cached by createActionContextInstance * method. </p> */ private Constructor servletActionContextConstructor = null; // ---------------------------------------------------------- Public Methods /** * <p>Clean up in preparation for a shutdown of this application.</p> */ public void destroy() { super.destroy(); catalogFactory = null; catalog = null; command = null; actionContextClass = null; servletActionContextConstructor = null; } /** * <p>Initialize this request processor instance.</p> * * @param servlet The ActionServlet we are associated with * @param moduleConfig The ModuleConfig we are associated with. * @throws ServletException If an error occurs during initialization */ public void init(ActionServlet servlet, ModuleConfig moduleConfig) throws ServletException { LOG.info( "Initializing composable request processor for module prefix '" + moduleConfig.getPrefix() + "'"); super.init(servlet, moduleConfig); initCatalogFactory(servlet, moduleConfig); ControllerConfig controllerConfig = moduleConfig.getControllerConfig(); String catalogName = controllerConfig.getCatalog(); catalog = this.catalogFactory.getCatalog(catalogName); if (catalog == null) { throw new ServletException("Cannot find catalog '" + catalogName + "'"); } String commandName = controllerConfig.getCommand(); command = catalog.getCommand(commandName); if (command == null) { throw new ServletException("Cannot find command '" + commandName + "'"); } this.setActionContextClassName(controllerConfig.getProperty( ACTION_CONTEXT_CLASS)); } /** * <p> Set and cache ActionContext class. </p><p> If there is a custom * class provided and if it uses our "preferred" constructor, cache a * reference to that constructor rather than looking it up every time. * </p> * * @param actionContextClass The ActionContext class to process */ private void setActionContextClass(Class actionContextClass) { this.actionContextClass = actionContextClass; if (actionContextClass != null) { this.servletActionContextConstructor = ConstructorUtils.getAccessibleConstructor(actionContextClass, SERVLET_ACTION_CONTEXT_CTOR_SIGNATURE); } else { this.servletActionContextConstructor = null; } } /** * <p>Make sure that the specified <code>className</code> identfies a * class which can be found and which implements the * <code>ActionContext</code> interface.</p> * * @param className Fully qualified name of * @throws ServletException If an error occurs during initialization * @throws UnavailableException if class does not implement ActionContext * or is not found */ private void setActionContextClassName(String className) throws ServletException { if ((className != null) && (className.trim().length() > 0)) { if (LOG.isDebugEnabled()) { LOG.debug( "setActionContextClassName: requested context class: " + className); } try { Class actionContextClass = RequestUtils.applicationClass(className); if (!ActionContext.class.isAssignableFrom(actionContextClass)) { throw new UnavailableException("ActionContextClass " + "[" + className + "]" + " must implement ActionContext interface."); } this.setActionContextClass(actionContextClass); } catch (ClassNotFoundException e) { throw new UnavailableException("ActionContextClass " + className + " not found."); } } else { if (LOG.isDebugEnabled()) { LOG.debug("setActionContextClassName: no className specified"); } this.setActionContextClass(null); } } /** * <p> Establish the CatalogFactory which will be used to look up the * catalog which has the request processing command. </p><p> The base * implementation simply calls CatalogFactory.getInstance(), unless the * catalogFactory property of this object has already been set, in which * case it is not changed. </p> * * @param servlet The ActionServlet we are processing * @param moduleConfig The ModuleConfig we are processing */ protected void initCatalogFactory(ActionServlet servlet, ModuleConfig moduleConfig) { if (this.catalogFactory != null) { return; } this.catalogFactory = CatalogFactory.getInstance(); } /** * <p>Process an <code>HttpServletRequest</code> and create the * corresponding <code>HttpServletResponse</code>.</p> * * @param request The servlet request we are processing * @param response The servlet response we are creating * @throws IOException if an input/output error occurs * @throws ServletException if a processing exception occurs */ public void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // Wrap the request in the case of a multipart request request = processMultipart(request); // Create and populate a Context for this request ActionContext context = contextInstance(request, response); // Create and execute the command. try { if (LOG.isDebugEnabled()) { LOG.debug("Using processing chain for this request"); } command.execute(context); } catch (Exception e) { // Execute the exception processing chain?? throw new ServletException(e); } // Release the context. context.release(); } /** * <p>Provide the initialized <code>ActionContext</code> instance which * will be used by this request. Internally, this simply calls * <code>createActionContextInstance</code> followed by * <code>initializeActionContext</code>.</p> * * @param request The servlet request we are processing * @param response The servlet response we are creating * @return Initiliazed ActionContext * @throws ServletException if a processing exception occurs */ protected ActionContext contextInstance(HttpServletRequest request, HttpServletResponse response) throws ServletException { ActionContext context = createActionContextInstance(getServletContext(), request, response); initializeActionContext(context); return context; } /** * <p>Create a new instance of <code>ActionContext</code> according to * configuration. If no alternative was specified at initialization, a * new instance <code>ServletActionContext</code> is returned. If an * alternative was specified using the <code>ACTION_CONTEXT_CLASS</code> * property, then that value is treated as a classname, and an instance of * that class is created. If that class implements the same constructor * that <code>ServletActionContext</code> does, then that constructor will * be used: <code>ServletContext, HttpServletRequest, * HttpServletResponse</code>; otherwise, it is assumed that the class has * a no-arguments constructor. If these constraints do not suit you, * simply override this method in a subclass.</p> * * @param servletContext The servlet context we are processing * @param request The servlet request we are processing * @param response The servlet response we are creating * @return New instance of ActionContext * @throws ServletException if a processing exception occurs */ protected ActionContext createActionContextInstance( ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) throws ServletException { if (this.actionContextClass == null) { return new ServletActionContext(servletContext, request, response); } try { if (this.servletActionContextConstructor == null) { return (ActionContext) this.actionContextClass.newInstance(); } return (ActionContext) this.servletActionContextConstructor .newInstance(new Object[] { servletContext, request, response }); } catch (Exception e) { throw new ServletException( "Error creating ActionContext instance of type " + this.actionContextClass, e); } } /** * <p>Set common properties on the given <code>ActionContext</code> * instance so that commands in the chain can count on their presence. * Note that while this method does not require that its argument be an * instance of <code>ServletActionContext</code>, at this time many common * Struts commands will be expecting to receive an <code>ActionContext</code> * which is also a <code>ServletActionContext</code>.</p> * * @param context The ActionContext we are processing */ protected void initializeActionContext(ActionContext context) { if (context instanceof ServletActionContext) { ((ServletActionContext) context).setActionServlet(this.servlet); } context.setModuleConfig(this.moduleConfig); } /** * <p>If this is a multipart request, wrap it with a special wrapper. * Otherwise, return the request unchanged.</p> * * @param request The HttpServletRequest we are processing * @return Original or wrapped request as appropriate */ protected HttpServletRequest processMultipart(HttpServletRequest request) { if (!"POST".equalsIgnoreCase(request.getMethod())) { return (request); } String contentType = request.getContentType(); if ((contentType != null) && contentType.startsWith("multipart/form-data")) { return (new MultipartRequestWrapper(request)); } else { return (request); } } /** * <p>Set the <code>CatalogFactory</code> instance which should be used to * find the request-processing command. In the base implementation, if * this value is not already set, then it will be initialized when {@link * #initCatalogFactory} is called. </p> * * @param catalogFactory Our CatalogFactory instance */ public void setCatalogFactory(CatalogFactory catalogFactory) { this.catalogFactory = catalogFactory; } }