From 7fa910509672270f952a250c4ed64b7db69ccdac Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 15 Oct 2009 10:31:47 +0000 Subject: [PATCH] SPR-6236: Reintroduce Struts support --- org.springframework.web.struts/build.xml | 6 + org.springframework.web.struts/ivy.xml | 92 ++++ .../tiles/ComponentControllerSupport.java | 180 ++++++++ .../servlet/view/tiles/TilesConfigurer.java | 149 +++++++ .../web/servlet/view/tiles/TilesJstlView.java | 53 +++ .../web/servlet/view/tiles/TilesView.java | 209 +++++++++ .../web/servlet/view/tiles/package.html | 10 + .../struts/ActionServletAwareProcessor.java | 72 ++++ .../web/struts/ActionSupport.java | 151 +++++++ .../struts/AutowiringRequestProcessor.java | 187 +++++++++ .../AutowiringTilesRequestProcessor.java | 169 ++++++++ .../web/struts/ContextLoaderPlugIn.java | 397 ++++++++++++++++++ .../web/struts/DelegatingActionProxy.java | 170 ++++++++ .../web/struts/DelegatingActionUtils.java | 212 ++++++++++ .../struts/DelegatingRequestProcessor.java | 201 +++++++++ .../DelegatingTilesRequestProcessor.java | 147 +++++++ .../web/struts/DispatchActionSupport.java | 151 +++++++ .../struts/LookupDispatchActionSupport.java | 150 +++++++ .../struts/MappingDispatchActionSupport.java | 150 +++++++ .../web/struts/SpringBindingActionForm.java | 288 +++++++++++++ .../web/struts/package-info.java | 28 ++ .../view/tiles/TestComponentController.java | 36 ++ .../servlet/view/tiles/TilesViewTests.java | 181 ++++++++ .../web/struts/StrutsSupportTests.java | 337 +++++++++++++++ .../web/struts/TestAction.java | 48 +++ .../src/test/resources/log4j.xml | 28 ++ .../view/tiles/context-messages.properties | 6 + .../tiles/context-messages_en_GB.properties | 2 + .../tiles/context-messages_en_US.properties | 5 + .../web/servlet/view/tiles/tiles-test.xml | 17 + .../web/struts/WEB-INF/action-servlet.xml | 10 + org.springframework.web.struts/template.mf | 19 + org.springframework.web.struts/web-struts.iml | 70 +++ 33 files changed, 3931 insertions(+) create mode 100644 org.springframework.web.struts/build.xml create mode 100644 org.springframework.web.struts/ivy.xml create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/ComponentControllerSupport.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesConfigurer.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesJstlView.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesView.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/package.html create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionServletAwareProcessor.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionSupport.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringRequestProcessor.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringTilesRequestProcessor.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/ContextLoaderPlugIn.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionProxy.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionUtils.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingRequestProcessor.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingTilesRequestProcessor.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/DispatchActionSupport.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/LookupDispatchActionSupport.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/MappingDispatchActionSupport.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/SpringBindingActionForm.java create mode 100644 org.springframework.web.struts/src/main/java/org/springframework/web/struts/package-info.java create mode 100644 org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TestComponentController.java create mode 100644 org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TilesViewTests.java create mode 100644 org.springframework.web.struts/src/test/java/org/springframework/web/struts/StrutsSupportTests.java create mode 100644 org.springframework.web.struts/src/test/java/org/springframework/web/struts/TestAction.java create mode 100644 org.springframework.web.struts/src/test/resources/log4j.xml create mode 100644 org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages.properties create mode 100644 org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_GB.properties create mode 100644 org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_US.properties create mode 100644 org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/tiles-test.xml create mode 100644 org.springframework.web.struts/src/test/resources/org/springframework/web/struts/WEB-INF/action-servlet.xml create mode 100644 org.springframework.web.struts/template.mf create mode 100644 org.springframework.web.struts/web-struts.iml diff --git a/org.springframework.web.struts/build.xml b/org.springframework.web.struts/build.xml new file mode 100644 index 00000000000..acfd7e6840c --- /dev/null +++ b/org.springframework.web.struts/build.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/org.springframework.web.struts/ivy.xml b/org.springframework.web.struts/ivy.xml new file mode 100644 index 00000000000..b068325b050 --- /dev/null +++ b/org.springframework.web.struts/ivy.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/ComponentControllerSupport.java b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/ComponentControllerSupport.java new file mode 100644 index 00000000000..45274b8a600 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/ComponentControllerSupport.java @@ -0,0 +1,180 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed 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.springframework.web.servlet.view.tiles; + +import java.io.File; +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.tiles.ComponentContext; +import org.apache.struts.tiles.ControllerSupport; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.support.RequestContextUtils; +import org.springframework.web.util.NestedServletException; +import org.springframework.web.util.WebUtils; + +/** + * Convenience class for Spring-aware Tiles component controllers. + * Provides a reference to the current Spring application context, + * e.g. for bean lookup or resource loading. + * + *

Derives from the Tiles {@link ControllerSupport} class rather than + * implementing the Tiles {@link org.apache.struts.tiles.Controller} interface + * in order to be compatible with Struts 1.1 and 1.2. Implements both Struts 1.1's + * perform and Struts 1.2's execute method accordingly. + * + * @author Juergen Hoeller + * @author Alef Arendsen + * @since 22.08.2003 + * @see org.springframework.web.context.support.WebApplicationObjectSupport + * @deprecated as of Spring 3.0 + */ +@Deprecated +public abstract class ComponentControllerSupport extends ControllerSupport { + + private WebApplicationContext webApplicationContext; + + private MessageSourceAccessor messageSourceAccessor; + + + /** + * This implementation delegates to execute, + * converting non-Servlet/IO Exceptions to ServletException. + *

This is the only execution method available in Struts 1.1. + * @see #execute + */ + @Override + public final void perform( + ComponentContext componentContext, HttpServletRequest request, + HttpServletResponse response, ServletContext servletContext) + throws ServletException, IOException { + + try { + execute(componentContext, request, response, servletContext); + } + catch (ServletException ex) { + throw ex; + } + catch (IOException ex) { + throw ex; + } + catch (Throwable ex) { + throw new NestedServletException("Execution of component controller failed", ex); + } + } + + /** + * This implementation delegates to doPerform, + * lazy-initializing the application context reference if necessary. + *

This is the preferred execution method in Struts 1.2. + * When running with Struts 1.1, it will be called by perform. + * @see #perform + * @see #doPerform + */ + @Override + public final void execute( + ComponentContext componentContext, HttpServletRequest request, + HttpServletResponse response, ServletContext servletContext) + throws Exception { + + synchronized (this) { + if (this.webApplicationContext == null) { + this.webApplicationContext = RequestContextUtils.getWebApplicationContext(request, servletContext); + this.messageSourceAccessor = new MessageSourceAccessor(this.webApplicationContext); + } + } + doPerform(componentContext, request, response); + } + + + /** + * Subclasses can override this for custom initialization behavior. + * Gets called on initialization of the context for this controller. + * @throws org.springframework.context.ApplicationContextException in case of initialization errors + * @throws org.springframework.beans.BeansException if thrown by application context methods + */ + protected void initApplicationContext() throws BeansException { + } + + /** + * Return the current Spring ApplicationContext. + */ + protected final ApplicationContext getApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return a MessageSourceAccessor for the application context + * used by this object, for easy message access. + */ + protected final MessageSourceAccessor getMessageSourceAccessor() { + return this.messageSourceAccessor; + } + + /** + * Return the current ServletContext. + */ + protected final ServletContext getServletContext() { + return this.webApplicationContext.getServletContext(); + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @return the File representing the temporary directory + */ + protected final File getTempDir() { + return WebUtils.getTempDir(getServletContext()); + } + + + /** + * Perform the preparation for the component, allowing for any Exception to be thrown. + * The ServletContext can be retrieved via getServletContext, if necessary. + * The Spring WebApplicationContext can be accessed via getWebApplicationContext. + *

This method will be called both in the Struts 1.1 and Struts 1.2 case, + * by perform or execute, respectively. + * @param componentContext current Tiles component context + * @param request current HTTP request + * @param response current HTTP response + * @throws Exception in case of errors + * @see org.apache.struts.tiles.Controller#perform + * @see #getServletContext + * @see #getWebApplicationContext + * @see #perform + * @see #execute + */ + protected abstract void doPerform( + ComponentContext componentContext, HttpServletRequest request, HttpServletResponse response) + throws Exception; + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesConfigurer.java b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesConfigurer.java new file mode 100644 index 00000000000..3ac220ce339 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesConfigurer.java @@ -0,0 +1,149 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed 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.springframework.web.servlet.view.tiles; + +import org.apache.struts.tiles.DefinitionsFactory; +import org.apache.struts.tiles.DefinitionsFactoryConfig; +import org.apache.struts.tiles.DefinitionsFactoryException; +import org.apache.struts.tiles.TilesUtil; +import org.apache.struts.tiles.xmlDefinition.I18nFactorySet; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.StringUtils; +import org.springframework.web.context.support.WebApplicationObjectSupport; + +/** + * Helper class to configure Tiles 1.x for the Spring Framework. See + * http://struts.apache.org + * for more information about Struts Tiles, which basically is a templating + * mechanism for JSP-based web applications. + * + *

NOTE: This TilesConfigurer class supports Tiles 1.x, + * a.k.a. "Struts Tiles", which comes as part of Struts 1.x. + * For Tiles 2.x support, check out + * {@link org.springframework.web.servlet.view.tiles2.TilesConfigurer}. + * + *

The TilesConfigurer simply configures a Tiles DefinitionsFactory using + * a set of files containing definitions, to be accessed by {@link TilesView} + * instances. + * + *

TilesViews can be managed by any {@link org.springframework.web.servlet.ViewResolver}. + * For simple convention-based view resolution, consider using + * {@link org.springframework.web.servlet.view.UrlBasedViewResolver} with the + * "viewClass" property set to "org.springframework.web.servlet.view.tiles.TilesView". + * + *

A typical TilesConfigurer bean definition looks as follows: + * + *

+ * <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles.TilesConfigurer">
+ *   <property name="definitions">
+ *     <list>
+ *       <value>/WEB-INF/defs/general.xml</value>
+ *       <value>/WEB-INF/defs/widgets.xml</value>
+ *       <value>/WEB-INF/defs/administrator.xml</value>
+ *       <value>/WEB-INF/defs/customer.xml</value>
+ *       <value>/WEB-INF/defs/templates.xml</value>
+ *     </list>
+ *   </property>
+ * </bean>
+ * + * The values in the list are the actual files containing the definitions. + * + * @author Alef Arendsen + * @author Juergen Hoeller + * @see TilesView + * @see org.springframework.web.servlet.view.UrlBasedViewResolver + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class TilesConfigurer extends WebApplicationObjectSupport implements InitializingBean { + + /** Definition URLs mapped to descriptions */ + private String[] definitions; + + /** Validate the Tiles definitions? */ + private boolean validateDefinitions = true; + + /** Factory class for Tiles */ + private Class factoryClass = I18nFactorySet.class; + + + /** + * Set the Tiles definitions, i.e. the list of files containing the definitions. + */ + public void setDefinitions(String[] definitions) { + this.definitions = definitions; + } + + /** + * Set whether to validate the Tiles XML definitions. Default is "true". + */ + public void setValidateDefinitions(boolean validateDefinitions) { + this.validateDefinitions = validateDefinitions; + } + + /** + * Set the factory class for Tiles. Default is I18nFactorySet. + * @see org.apache.struts.tiles.xmlDefinition.I18nFactorySet + */ + public void setFactoryClass(Class factoryClass) { + this.factoryClass = factoryClass; + } + + + /** + * Initialize the Tiles definition factory. + * Delegates to createDefinitionsFactory for the actual creation. + * @throws DefinitionsFactoryException if an error occurs + * @see #createDefinitionsFactory + */ + public void afterPropertiesSet() throws DefinitionsFactoryException { + logger.debug("TilesConfigurer: initializion started"); + + // initialize the configuration for the definitions factory + DefinitionsFactoryConfig factoryConfig = new DefinitionsFactoryConfig(); + factoryConfig.setFactoryName(""); + factoryConfig.setFactoryClassname(this.factoryClass.getName()); + factoryConfig.setParserValidate(this.validateDefinitions); + + if (this.definitions != null) { + String defs = StringUtils.arrayToCommaDelimitedString(this.definitions); + if (logger.isInfoEnabled()) { + logger.info("TilesConfigurer: adding definitions [" + defs + "]"); + } + factoryConfig.setDefinitionConfigFiles(defs); + } + + // initialize the definitions factory + createDefinitionsFactory(factoryConfig); + + logger.debug("TilesConfigurer: initialization completed"); + } + + /** + * Create the Tiles DefinitionsFactory and expose it to the ServletContext. + * @param factoryConfig the configuration for the DefinitionsFactory + * @return the DefinitionsFactory + * @throws DefinitionsFactoryException if an error occurs + */ + protected DefinitionsFactory createDefinitionsFactory(DefinitionsFactoryConfig factoryConfig) + throws DefinitionsFactoryException { + + return TilesUtil.createDefinitionsFactory(getServletContext(), factoryConfig); + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesJstlView.java b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesJstlView.java new file mode 100644 index 00000000000..232f1b49ff0 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesJstlView.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed 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.springframework.web.servlet.view.tiles; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.servlet.support.JstlUtils; +import org.springframework.web.servlet.support.RequestContext; + +/** + * Specialization of {@link TilesView} for JSTL pages, + * i.e. Tiles pages that use the JSP Standard Tag Library. + * + *

NOTE: This TilesJstlView class supports Tiles 1.x, + * a.k.a. "Struts Tiles", which comes as part of Struts 1.x. + * For Tiles 2.x support, check out + * {@link org.springframework.web.servlet.view.tiles2.TilesView}. + * + *

Exposes JSTL-specific request attributes specifying locale + * and resource bundle for JSTL's formatting and message tags, + * using Spring's locale and message source. + * + *

This is a separate class mainly to avoid JSTL dependencies + * in TilesView itself. + * + * @author Juergen Hoeller + * @since 20.08.2003 + * @see org.springframework.web.servlet.support.JstlUtils#exposeLocalizationContext + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class TilesJstlView extends TilesView { + + @Override + protected void exposeHelpers(HttpServletRequest request) throws Exception { + JstlUtils.exposeLocalizationContext(new RequestContext(request, getServletContext())); + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesView.java b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesView.java new file mode 100644 index 00000000000..2ec68703757 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/TilesView.java @@ -0,0 +1,209 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed 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.springframework.web.servlet.view.tiles; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.tiles.ComponentContext; +import org.apache.struts.tiles.ComponentDefinition; +import org.apache.struts.tiles.Controller; +import org.apache.struts.tiles.DefinitionsFactory; +import org.apache.struts.tiles.TilesUtilImpl; + +import org.springframework.context.ApplicationContextException; +import org.springframework.web.servlet.view.InternalResourceView; + +/** + * View implementation that retrieves a Tiles definition. + * The "url" property is interpreted as name of a Tiles definition. + * + *

{@link TilesJstlView} with JSTL support is a separate class, + * mainly to avoid JSTL dependencies in this class. + * + *

NOTE: This TilesView class supports Tiles 1.x, + * a.k.a. "Struts Tiles", which comes as part of Struts 1.x. + * For Tiles 2.x support, check out + * {@link org.springframework.web.servlet.view.tiles2.TilesView}. + * + *

Depends on a Tiles DefinitionsFactory which must be available + * in the ServletContext. This factory is typically set up via a + * {@link TilesConfigurer} bean definition in the application context. + * + *

Check out {@link ComponentControllerSupport} which provides + * a convenient base class for Spring-aware component controllers, + * allowing convenient access to the Spring ApplicationContext. + * + * @author Alef Arendsen + * @author Juergen Hoeller + * @see #setUrl + * @see TilesJstlView + * @see TilesConfigurer + * @see ComponentControllerSupport + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class TilesView extends InternalResourceView { + + /** + * Name of the attribute that will override the path of the layout page + * to render. A Tiles component controller can set such an attribute + * to dynamically switch the look and feel of a Tiles page. + * @see #setPath + */ + public static final String PATH_ATTRIBUTE = TilesView.class.getName() + ".PATH"; + + /** + * Set the path of the layout page to render. + * @param request current HTTP request + * @param path the path of the layout page + * @see #PATH_ATTRIBUTE + */ + public static void setPath(HttpServletRequest request, String path) { + request.setAttribute(PATH_ATTRIBUTE, path); + } + + + private DefinitionsFactory definitionsFactory; + + + @Override + protected void initApplicationContext() throws ApplicationContextException { + super.initApplicationContext(); + + // get definitions factory + this.definitionsFactory = + (DefinitionsFactory) getServletContext().getAttribute(TilesUtilImpl.DEFINITIONS_FACTORY); + if (this.definitionsFactory == null) { + throw new ApplicationContextException("Tiles definitions factory not found: TilesConfigurer not defined?"); + } + } + + /** + * Prepare for rendering the Tiles definition: Execute the associated + * component controller if any, and determine the request dispatcher path. + */ + @Override + protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response) + throws Exception { + + // get component definition + ComponentDefinition definition = getComponentDefinition(this.definitionsFactory, request); + if (definition == null) { + throw new ServletException("No Tiles definition found for name '" + getUrl() + "'"); + } + + // get current component context + ComponentContext context = getComponentContext(definition, request); + + // execute component controller associated with definition, if any + Controller controller = getController(definition, request); + if (controller != null) { + if (logger.isDebugEnabled()) { + logger.debug("Executing Tiles controller [" + controller + "]"); + } + executeController(controller, context, request, response); + } + + // determine the path of the definition + String path = getDispatcherPath(definition, request); + if (path == null) { + throw new ServletException( + "Could not determine a path for Tiles definition '" + definition.getName() + "'"); + } + + return path; + } + + /** + * Determine the Tiles component definition for the given Tiles + * definitions factory. + * @param factory the Tiles definitions factory + * @param request current HTTP request + * @return the component definition + */ + protected ComponentDefinition getComponentDefinition(DefinitionsFactory factory, HttpServletRequest request) + throws Exception { + return factory.getDefinition(getUrl(), request, getServletContext()); + } + + /** + * Determine the Tiles component context for the given Tiles definition. + * @param definition the Tiles definition to render + * @param request current HTTP request + * @return the component context + * @throws Exception if preparations failed + */ + protected ComponentContext getComponentContext(ComponentDefinition definition, HttpServletRequest request) + throws Exception { + ComponentContext context = ComponentContext.getContext(request); + if (context == null) { + context = new ComponentContext(definition.getAttributes()); + ComponentContext.setContext(context, request); + } + else { + context.addMissing(definition.getAttributes()); + } + return context; + } + + /** + * Determine and initialize the Tiles component controller for the + * given Tiles definition, if any. + * @param definition the Tiles definition to render + * @param request current HTTP request + * @return the component controller to execute, or null if none + * @throws Exception if preparations failed + */ + protected Controller getController(ComponentDefinition definition, HttpServletRequest request) + throws Exception { + + return definition.getOrCreateController(); + } + + /** + * Execute the given Tiles controller. + * @param controller the component controller to execute + * @param context the component context + * @param request current HTTP request + * @param response current HTTP response + * @throws Exception if controller execution failed + */ + protected void executeController( + Controller controller, ComponentContext context, HttpServletRequest request, HttpServletResponse response) + throws Exception { + + controller.perform(context, request, response, getServletContext()); + } + + /** + * Determine the dispatcher path for the given Tiles definition, + * i.e. the request dispatcher path of the layout page. + * @param definition the Tiles definition to render + * @param request current HTTP request + * @return the path of the layout page to render + * @throws Exception if preparations failed + */ + protected String getDispatcherPath(ComponentDefinition definition, HttpServletRequest request) + throws Exception { + + Object pathAttr = request.getAttribute(PATH_ATTRIBUTE); + return (pathAttr != null ? pathAttr.toString() : definition.getPath()); + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/package.html b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/package.html new file mode 100644 index 00000000000..4320c5f3eaf --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/servlet/view/tiles/package.html @@ -0,0 +1,10 @@ + + + +Support classes for the integration of +Tiles +(included in Struts) as Spring web view technology. +Contains a View implementation for Tiles definitions. + + + diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionServletAwareProcessor.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionServletAwareProcessor.java new file mode 100644 index 00000000000..e49b12be6d7 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionServletAwareProcessor.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionServlet; + +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; + +/** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} + * implementation that passes the ActionServlet to beans that extend + * the Struts {@link org.apache.struts.action.Action} class. + * Invokes Action.setServlet with null on + * bean destruction, providing the same lifecycle handling as the + * native Struts ActionServlet. + * + *

ContextLoaderPlugIn automatically registers this processor + * with the underlying bean factory of its WebApplicationContext. + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see ContextLoaderPlugIn + * @see org.apache.struts.action.Action#setServlet + * @deprecated as of Spring 3.0 + */ +@Deprecated +class ActionServletAwareProcessor implements DestructionAwareBeanPostProcessor { + + private final ActionServlet actionServlet; + + + /** + * Create a new ActionServletAwareProcessor for the given servlet. + */ + public ActionServletAwareProcessor(ActionServlet actionServlet) { + this.actionServlet = actionServlet; + } + + + public Object postProcessBeforeInitialization(Object bean, String beanName) { + if (bean instanceof Action) { + ((Action) bean).setServlet(this.actionServlet); + } + return bean; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) { + return bean; + } + + public void postProcessBeforeDestruction(Object bean, String beanName) { + if (bean instanceof Action) { + ((Action) bean).setServlet(null); + } + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionSupport.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionSupport.java new file mode 100644 index 00000000000..1630b30c77a --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ActionSupport.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.File; + +import javax.servlet.ServletContext; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionServlet; + +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.util.WebUtils; + +/** + * Convenience class for Spring-aware Struts 1.1+ Actions. + * + *

Provides a reference to the current Spring application context, e.g. + * for bean lookup or resource loading. Auto-detects a ContextLoaderPlugIn + * context, falling back to the root WebApplicationContext. For typical + * usage, i.e. accessing middle tier beans, use a root WebApplicationContext. + * + *

For Struts DispatchActions or Lookup/MappingDispatchActions, use the + * analogous {@link DispatchActionSupport DispatchActionSupport} or + * {@link LookupDispatchActionSupport LookupDispatchActionSupport} / + * {@link MappingDispatchActionSupport MappingDispatchActionSupport} class, + * respectively. + * + *

As an alternative approach, you can wire your Struts Actions themselves + * as Spring beans, passing references to them via IoC rather than looking + * up references in a programmatic fashion. Check out + * {@link DelegatingActionProxy DelegatingActionProxy} and + * {@link DelegatingRequestProcessor DelegatingRequestProcessor}. + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + * @see org.springframework.web.context.ContextLoaderListener + * @see org.springframework.web.context.ContextLoaderServlet + * @see DispatchActionSupport + * @see LookupDispatchActionSupport + * @see MappingDispatchActionSupport + * @see DelegatingActionProxy + * @see DelegatingRequestProcessor + * @deprecated as of Spring 3.0 + */ +@Deprecated +public abstract class ActionSupport extends Action { + + private WebApplicationContext webApplicationContext; + + private MessageSourceAccessor messageSourceAccessor; + + + /** + * Initialize the WebApplicationContext for this Action. + * Invokes onInit after successful initialization of the context. + * @see #initWebApplicationContext + * @see #onInit + */ + @Override + public void setServlet(ActionServlet actionServlet) { + super.setServlet(actionServlet); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet); + this.messageSourceAccessor = new MessageSourceAccessor(this.webApplicationContext); + onInit(); + } + else { + onDestroy(); + } + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext, + * falling back to the root WebApplicationContext (the usual case). + * @param actionServlet the associated ActionServlet + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + */ + protected WebApplicationContext initWebApplicationContext(ActionServlet actionServlet) + throws IllegalStateException { + + return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, null); + } + + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return a MessageSourceAccessor for the application context + * used by this object, for easy message access. + */ + protected final MessageSourceAccessor getMessageSourceAccessor() { + return this.messageSourceAccessor; + } + + /** + * Return the current ServletContext. + */ + protected final ServletContext getServletContext() { + return this.webApplicationContext.getServletContext(); + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @return the File representing the temporary directory + */ + protected final File getTempDir() { + return WebUtils.getTempDir(getServletContext()); + } + + + /** + * Callback for custom initialization after the context has been set up. + * @see #setServlet + */ + protected void onInit() { + } + + /** + * Callback for custom destruction when the ActionServlet shuts down. + * @see #setServlet + */ + protected void onDestroy() { + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringRequestProcessor.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringRequestProcessor.java new file mode 100644 index 00000000000..f14091729d9 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringRequestProcessor.java @@ -0,0 +1,187 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.action.RequestProcessor; +import org.apache.struts.config.ModuleConfig; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.web.context.WebApplicationContext; + +/** + * Subclass of Struts's default RequestProcessor that autowires Struts Actions + * with Spring beans defined in ContextLoaderPlugIn's WebApplicationContext + * or - in case of general service layer beans - in the root WebApplicationContext. + * + *

In the Struts config file, you simply continue to specify the original + * Action class. The instance created for that class will automatically get + * wired with matching service layer beans, that is, bean property setters + * will automatically be called if a service layer bean matches the property. + * + *

+ * <action path="/login" type="myapp.MyAction"/>
+ * + * There are two autowire modes available: "byType" and "byName". The default + * is "byType", matching service layer beans with the Action's bean property + * argument types. This behavior can be changed through specifying an "autowire" + * init-param for the Struts ActionServlet with the value "byName", which will + * match service layer bean names with the Action's bean property names. + * + *

Dependency checking is turned off by default: If no matching service + * layer bean can be found, the setter in question will simply not get invoked. + * To enforce matching service layer beans, consider specify the "dependencyCheck" + * init-param for the Struts ActionServlet with the value "true". + * + *

If you also need the Tiles setup functionality of the original + * TilesRequestProcessor, use AutowiringTilesRequestProcessor. As there's just + * a single central class to customize in Struts, we have to provide another + * subclass here, covering both the Tiles and the Spring delegation aspect. + * + *

The default implementation delegates to the DelegatingActionUtils + * class as fas as possible, to reuse as much code as possible despite + * the need to provide two RequestProcessor subclasses. If you need to + * subclass yet another RequestProcessor, take this class as a template, + * delegating to DelegatingActionUtils just like it. + * + * @author Juergen Hoeller + * @since 2.0 + * @see AutowiringTilesRequestProcessor + * @see ContextLoaderPlugIn + * @see DelegatingActionUtils + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class AutowiringRequestProcessor extends RequestProcessor { + + private WebApplicationContext webApplicationContext; + + private int autowireMode = AutowireCapableBeanFactory.AUTOWIRE_NO; + + private boolean dependencyCheck = false; + + + @Override + public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException { + super.init(actionServlet, moduleConfig); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet, moduleConfig); + this.autowireMode = initAutowireMode(actionServlet, moduleConfig); + this.dependencyCheck = initDependencyCheck(actionServlet, moduleConfig); + } + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext, + * falling back to the root WebApplicationContext. This context is supposed + * to contain the service layer beans to wire the Struts Actions with. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + */ + protected WebApplicationContext initWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { + + WebApplicationContext wac = + DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig); + if (wac instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) wac).getBeanFactory().ignoreDependencyType(ActionServlet.class); + } + return wac; + } + + /** + * Determine the autowire mode to use for wiring Struts Actions. + *

The default implementation checks the "autowire" init-param of the + * Struts ActionServlet, falling back to "AUTOWIRE_BY_TYPE" as default. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return the autowire mode to use + * @see DelegatingActionUtils#getAutowireMode + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME + */ + protected int initAutowireMode(ActionServlet actionServlet, ModuleConfig moduleConfig) { + return DelegatingActionUtils.getAutowireMode(actionServlet); + } + + /** + * Determine whether to apply a dependency check after wiring Struts Actions. + *

The default implementation checks the "dependencyCheck" init-param of the + * Struts ActionServlet, falling back to no dependency check as default. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return whether to enforce a dependency check or not + * @see DelegatingActionUtils#getDependencyCheck + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + */ + protected boolean initDependencyCheck(ActionServlet actionServlet, ModuleConfig moduleConfig) { + return DelegatingActionUtils.getDependencyCheck(actionServlet); + } + + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return the autowire mode to use for wiring Struts Actions. + */ + protected final int getAutowireMode() { + return autowireMode; + } + + /** + * Return whether to apply a dependency check after wiring Struts Actions. + */ + protected final boolean getDependencyCheck() { + return dependencyCheck; + } + + + /** + * Extend the base class method to autowire each created Action instance. + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + */ + @Override + protected Action processActionCreate( + HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) + throws IOException { + + Action action = super.processActionCreate(request, response, mapping); + getWebApplicationContext().getAutowireCapableBeanFactory().autowireBeanProperties( + action, getAutowireMode(), getDependencyCheck()); + return action; + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringTilesRequestProcessor.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringTilesRequestProcessor.java new file mode 100644 index 00000000000..96a24c8c6e8 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/AutowiringTilesRequestProcessor.java @@ -0,0 +1,169 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.config.ModuleConfig; +import org.apache.struts.tiles.TilesRequestProcessor; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.web.context.WebApplicationContext; + +/** + * Subclass of Struts's TilesRequestProcessor that autowires Struts Actions + * with Spring beans defined in ContextLoaderPlugIn's WebApplicationContext + * or - in case of general service layer beans - in the root WebApplicationContext. + * + *

Behaves like + * {@link AutowiringRequestProcessor AutowiringRequestProcessor}, + * but also provides the Tiles functionality of the original TilesRequestProcessor. + * As there's just a single central class to customize in Struts, we have to provide + * another subclass here, covering both the Tiles and the Spring delegation aspect. + * + *

The default implementation delegates to the DelegatingActionUtils + * class as fas as possible, to reuse as much code as possible despite + * the need to provide two RequestProcessor subclasses. If you need to + * subclass yet another RequestProcessor, take this class as a template, + * delegating to DelegatingActionUtils just like it. + * + * @author Juergen Hoeller + * @since 2.0 + * @see AutowiringRequestProcessor + * @see ContextLoaderPlugIn + * @see DelegatingActionUtils + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class AutowiringTilesRequestProcessor extends TilesRequestProcessor { + + private WebApplicationContext webApplicationContext; + + private int autowireMode = AutowireCapableBeanFactory.AUTOWIRE_NO; + + private boolean dependencyCheck = false; + + + @Override + public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException { + super.init(actionServlet, moduleConfig); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet, moduleConfig); + this.autowireMode = initAutowireMode(actionServlet, moduleConfig); + this.dependencyCheck = initDependencyCheck(actionServlet, moduleConfig); + } + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext, + * falling back to the root WebApplicationContext. This context is supposed + * to contain the service layer beans to wire the Struts Actions with. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + */ + protected WebApplicationContext initWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { + + WebApplicationContext wac = + DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig); + if (wac instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) wac).getBeanFactory().ignoreDependencyType(ActionServlet.class); + } + return wac; + } + + /** + * Determine the autowire mode to use for wiring Struts Actions. + *

The default implementation checks the "autowire" init-param of the + * Struts ActionServlet, falling back to "AUTOWIRE_BY_TYPE" as default. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return the autowire mode to use + * @see DelegatingActionUtils#getAutowireMode + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME + */ + protected int initAutowireMode(ActionServlet actionServlet, ModuleConfig moduleConfig) { + return DelegatingActionUtils.getAutowireMode(actionServlet); + } + + /** + * Determine whether to apply a dependency check after wiring Struts Actions. + *

The default implementation checks the "dependencyCheck" init-param of the + * Struts ActionServlet, falling back to no dependency check as default. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return whether to enforce a dependency check or not + * @see DelegatingActionUtils#getDependencyCheck + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + */ + protected boolean initDependencyCheck(ActionServlet actionServlet, ModuleConfig moduleConfig) { + return DelegatingActionUtils.getDependencyCheck(actionServlet); + } + + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return the autowire mode to use for wiring Struts Actions. + */ + protected final int getAutowireMode() { + return autowireMode; + } + + /** + * Return whether to apply a dependency check after wiring Struts Actions. + */ + protected final boolean getDependencyCheck() { + return dependencyCheck; + } + + + /** + * Extend the base class method to autowire each created Action instance. + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + */ + @Override + protected Action processActionCreate( + HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) + throws IOException { + + Action action = super.processActionCreate(request, response, mapping); + getWebApplicationContext().getAutowireCapableBeanFactory().autowireBeanProperties( + action, getAutowireMode(), getDependencyCheck()); + return action; + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ContextLoaderPlugIn.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ContextLoaderPlugIn.java new file mode 100644 index 00000000000..24317165dbc --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/ContextLoaderPlugIn.java @@ -0,0 +1,397 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.action.PlugIn; +import org.apache.struts.config.ModuleConfig; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContextException; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.context.support.XmlWebApplicationContext; + +/** + * Struts 1.1+ PlugIn that loads a Spring application context for the Struts + * ActionServlet. This context will automatically refer to the root + * WebApplicationContext (loaded by ContextLoaderListener/Servlet) as parent. + * + *

The default namespace of the WebApplicationContext is the name of the + * Struts ActionServlet, suffixed with "-servlet" (e.g. "action-servlet"). + * The default location of the XmlWebApplicationContext configuration file + * is therefore "/WEB-INF/action-servlet.xml". + * + *

+ * <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/>
+ * + * The location of the context configuration files can be customized + * through the "contextConfigLocation" setting, analogous to the root + * WebApplicationContext and FrameworkServlet contexts. + * + *
+ * <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
+ *   <set-property property="contextConfigLocation" value="/WEB-INF/action-servlet.xml /WEB-INF/myContext.xml"/>
+ * </plug-in>
+ * + * Beans defined in the ContextLoaderPlugIn context can be accessed + * from conventional Struts Actions, via fetching the WebApplicationContext + * reference from the ServletContext. ActionSupport and DispatchActionSupport + * are pre-built convenience classes that provide easy access to the context. + * + *

It is normally preferable to access Spring's root WebApplicationContext + * in such scenarios, though: A shared middle tier should be defined there + * rather than in a ContextLoaderPlugin context, for access by any web component. + * ActionSupport and DispatchActionSupport autodetect the root context too. + * + *

A special usage of this PlugIn is to define Struts Actions themselves + * as beans, typically wiring them with middle tier components defined in the + * root context. Such Actions will then be delegated to by proxy definitions + * in the Struts configuration, using the DelegatingActionProxy class or + * the DelegatingRequestProcessor. + * + *

Note that you can use a single ContextLoaderPlugIn for all Struts modules. + * That context can in turn be loaded from multiple XML files, for example split + * according to Struts modules. Alternatively, define one ContextLoaderPlugIn per + * Struts module, specifying appropriate "contextConfigLocation" parameters. + * + *

Note: The idea of delegating to Spring-managed Struts Actions originated in + * Don Brown's Spring Struts Plugin. + * ContextLoaderPlugIn and DelegatingActionProxy constitute a clean-room + * implementation of the same idea, essentially superseding the original plugin. + * Many thanks to Don Brown and Matt Raible for the original work and for the + * agreement to reimplement the idea in Spring proper! + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see #SERVLET_CONTEXT_PREFIX + * @see ActionSupport + * @see DispatchActionSupport + * @see DelegatingActionProxy + * @see DelegatingRequestProcessor + * @see DelegatingTilesRequestProcessor + * @see org.springframework.web.context.ContextLoaderListener + * @see org.springframework.web.context.ContextLoaderServlet + * @see org.springframework.web.servlet.FrameworkServlet + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class ContextLoaderPlugIn implements PlugIn { + + /** + * Suffix for WebApplicationContext namespaces. If a Struts ActionServlet is + * given the name "action" in a context, the namespace used by this PlugIn will + * resolve to "action-servlet". + */ + public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet"; + + /** + * Default context class for ContextLoaderPlugIn. + * @see org.springframework.web.context.support.XmlWebApplicationContext + */ + public static final Class DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; + + /** + * Prefix for the ServletContext attribute for the WebApplicationContext. + * The completion is the Struts module name. + */ + public static final String SERVLET_CONTEXT_PREFIX = ContextLoaderPlugIn.class.getName() + ".CONTEXT."; + + + protected final Log logger = LogFactory.getLog(getClass()); + + /** Custom WebApplicationContext class */ + private Class contextClass = DEFAULT_CONTEXT_CLASS; + + /** Namespace for this servlet */ + private String namespace; + + /** Explicit context config location */ + private String contextConfigLocation; + + /** The Struts ActionServlet that this PlugIn is registered with */ + private ActionServlet actionServlet; + + /** The Struts ModuleConfig that this PlugIn is registered with */ + private ModuleConfig moduleConfig; + + /** WebApplicationContext for the ActionServlet */ + private WebApplicationContext webApplicationContext; + + + /** + * Set a custom context class by name. This class must be of type WebApplicationContext, + * when using the default ContextLoaderPlugIn implementation, the context class + * must also implement ConfigurableWebApplicationContext. + * @see #createWebApplicationContext + */ + public void setContextClassName(String contextClassName) throws ClassNotFoundException { + this.contextClass = ClassUtils.forName(contextClassName); + } + + /** + * Set a custom context class. This class must be of type WebApplicationContext, + * when using the default ContextLoaderPlugIn implementation, the context class + * must also implement ConfigurableWebApplicationContext. + * @see #createWebApplicationContext + */ + public void setContextClass(Class contextClass) { + this.contextClass = contextClass; + } + + /** + * Return the custom context class. + */ + public Class getContextClass() { + return this.contextClass; + } + + /** + * Set a custom namespace for the ActionServlet, + * to be used for building a default context config location. + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Return the namespace for the ActionServlet, falling back to default scheme if + * no custom namespace was set: e.g. "test-servlet" for a servlet named "test". + */ + public String getNamespace() { + if (this.namespace != null) { + return this.namespace; + } + if (this.actionServlet != null) { + return this.actionServlet.getServletName() + DEFAULT_NAMESPACE_SUFFIX; + } + return null; + } + + /** + * Set the context config location explicitly, instead of relying on the default + * location built from the namespace. This location string can consist of + * multiple locations separated by any number of commas and spaces. + */ + public void setContextConfigLocation(String contextConfigLocation) { + this.contextConfigLocation = contextConfigLocation; + } + + /** + * Return the explicit context config location, if any. + */ + public String getContextConfigLocation() { + return this.contextConfigLocation; + } + + + /** + * Create the ActionServlet's WebApplicationContext. + */ + public final void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException { + long startTime = System.currentTimeMillis(); + if (logger.isInfoEnabled()) { + logger.info("ContextLoaderPlugIn for Struts ActionServlet '" + actionServlet.getServletName() + + ", module '" + moduleConfig.getPrefix() + "': initialization started"); + } + + this.actionServlet = actionServlet; + this.moduleConfig = moduleConfig; + try { + this.webApplicationContext = initWebApplicationContext(); + onInit(); + } + catch (RuntimeException ex) { + logger.error("Context initialization failed", ex); + throw ex; + } + + if (logger.isInfoEnabled()) { + long elapsedTime = System.currentTimeMillis() - startTime; + logger.info("ContextLoaderPlugIn for Struts ActionServlet '" + actionServlet.getServletName() + + "', module '" + moduleConfig.getPrefix() + "': initialization completed in " + elapsedTime + " ms"); + } + } + + /** + * Return the Struts ActionServlet that this PlugIn is associated with. + */ + public final ActionServlet getActionServlet() { + return actionServlet; + } + + /** + * Return the name of the ActionServlet that this PlugIn is associated with. + */ + public final String getServletName() { + return this.actionServlet.getServletName(); + } + + /** + * Return the ServletContext that this PlugIn is associated with. + */ + public final ServletContext getServletContext() { + return this.actionServlet.getServletContext(); + } + + /** + * Return the Struts ModuleConfig that this PlugIn is associated with. + */ + public final ModuleConfig getModuleConfig() { + return this.moduleConfig; + } + + /** + * Return the prefix of the ModuleConfig that this PlugIn is associated with. + * @see org.apache.struts.config.ModuleConfig#getPrefix + */ + public final String getModulePrefix() { + return this.moduleConfig.getPrefix(); + } + + /** + * Initialize and publish the WebApplicationContext for the ActionServlet. + *

Delegates to {@link #createWebApplicationContext} for actual creation. + *

Can be overridden in subclasses. Call getActionServlet() + * and/or getModuleConfig() to access the Struts configuration + * that this PlugIn is associated with. + * @throws org.springframework.beans.BeansException if the context couldn't be initialized + * @throws IllegalStateException if there is already a context for the Struts ActionServlet + * @see #getActionServlet() + * @see #getServletName() + * @see #getServletContext() + * @see #getModuleConfig() + * @see #getModulePrefix() + */ + protected WebApplicationContext initWebApplicationContext() throws BeansException, IllegalStateException { + getServletContext().log("Initializing WebApplicationContext for Struts ActionServlet '" + + getServletName() + "', module '" + getModulePrefix() + "'"); + WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); + + WebApplicationContext wac = createWebApplicationContext(parent); + if (logger.isInfoEnabled()) { + logger.info("Using context class '" + wac.getClass().getName() + "' for servlet '" + getServletName() + "'"); + } + + // Publish the context as a servlet context attribute. + String attrName = getServletContextAttributeName(); + getServletContext().setAttribute(attrName, wac); + if (logger.isDebugEnabled()) { + logger.debug("Published WebApplicationContext of Struts ActionServlet '" + getServletName() + + "', module '" + getModulePrefix() + "' as ServletContext attribute with name [" + attrName + "]"); + } + + return wac; + } + + /** + * Instantiate the WebApplicationContext for the ActionServlet, either a default + * XmlWebApplicationContext or a custom context class if set. + *

This implementation expects custom contexts to implement ConfigurableWebApplicationContext. + * Can be overridden in subclasses. + * @throws org.springframework.beans.BeansException if the context couldn't be initialized + * @see #setContextClass + * @see org.springframework.web.context.support.XmlWebApplicationContext + */ + protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) + throws BeansException { + + if (logger.isDebugEnabled()) { + logger.debug("ContextLoaderPlugIn for Struts ActionServlet '" + getServletName() + + "', module '" + getModulePrefix() + "' will try to create custom WebApplicationContext " + + "context of class '" + getContextClass().getName() + "', using parent context [" + parent + "]"); + } + if (!ConfigurableWebApplicationContext.class.isAssignableFrom(getContextClass())) { + throw new ApplicationContextException( + "Fatal initialization error in ContextLoaderPlugIn for Struts ActionServlet '" + getServletName() + + "', module '" + getModulePrefix() + "': custom WebApplicationContext class [" + + getContextClass().getName() + "] is not of type ConfigurableWebApplicationContext"); + } + + ConfigurableWebApplicationContext wac = + (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass()); + wac.setParent(parent); + wac.setServletContext(getServletContext()); + wac.setNamespace(getNamespace()); + if (getContextConfigLocation() != null) { + wac.setConfigLocations( + StringUtils.tokenizeToStringArray( + getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS)); + } + wac.addBeanFactoryPostProcessor( + new BeanFactoryPostProcessor() { + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + beanFactory.addBeanPostProcessor(new ActionServletAwareProcessor(getActionServlet())); + beanFactory.ignoreDependencyType(ActionServlet.class); + } + } + ); + + wac.refresh(); + return wac; + } + + /** + * Return the ServletContext attribute name for this PlugIn's WebApplicationContext. + *

The default implementation returns SERVLET_CONTEXT_PREFIX + module prefix. + * @see #SERVLET_CONTEXT_PREFIX + * @see #getModulePrefix() + */ + public String getServletContextAttributeName() { + return SERVLET_CONTEXT_PREFIX + getModulePrefix(); + } + + /** + * Return this PlugIn's WebApplicationContext. + */ + public final WebApplicationContext getWebApplicationContext() { + return webApplicationContext; + } + + /** + * Callback for custom initialization after the context has been set up. + * @throws ServletException if initialization failed + */ + protected void onInit() throws ServletException { + } + + + /** + * Close the WebApplicationContext of the ActionServlet. + * @see org.springframework.context.ConfigurableApplicationContext#close() + */ + public void destroy() { + getServletContext().log("Closing WebApplicationContext of Struts ActionServlet '" + + getServletName() + "', module '" + getModulePrefix() + "'"); + if (getWebApplicationContext() instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) getWebApplicationContext()).close(); + } + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionProxy.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionProxy.java new file mode 100644 index 00000000000..7070cf089fd --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionProxy.java @@ -0,0 +1,170 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.config.ModuleConfig; + +import org.springframework.beans.BeansException; +import org.springframework.web.context.WebApplicationContext; + +/** + * Proxy for a Spring-managed Struts Action that is defined in + * {@link ContextLoaderPlugIn ContextLoaderPlugIn's} + * {@link WebApplicationContext}. + * + *

The proxy is defined in the Struts config file, specifying this + * class as the action class. This class will delegate to a Struts + * Action bean in the ContextLoaderPlugIn context. + * + *

<action path="/login" type="org.springframework.web.struts.DelegatingActionProxy"/>
+ * + * The name of the Action bean in the + * WebApplicationContext will be determined from the mapping + * path and module prefix. This can be customized by overriding the + * determineActionBeanName method. + * + *

Example: + *

+ * + *

A corresponding bean definition in the ContextLoaderPlugin + * context would look as follows; notice that the Action is now + * able to leverage fully Spring's configuration facilities: + * + *

+ * <bean name="/login" class="myapp.MyAction">
+ *   <property name="...">...</property>
+ * </bean>
+ * + * Note that you can use a single ContextLoaderPlugIn for all + * Struts modules. That context can in turn be loaded from multiple XML files, + * for example split according to Struts modules. Alternatively, define one + * ContextLoaderPlugIn per Struts module, specifying appropriate + * "contextConfigLocation" parameters. In both cases, the Spring bean name + * has to include the module prefix. + * + *

If you want to avoid having to specify DelegatingActionProxy + * as the Action type in your struts-config file (for example to + * be able to generate your Struts config file with XDoclet) consider using the + * {@link DelegatingRequestProcessor DelegatingRequestProcessor}. + * The latter's disadvantage is that it might conflict with the need + * for a different RequestProcessor subclass. + * + *

The default implementation delegates to the {@link DelegatingActionUtils} + * class as much as possible, to reuse as much code as possible with + * DelegatingRequestProcessor and + * {@link DelegatingTilesRequestProcessor}. + * + *

Note: The idea of delegating to Spring-managed Struts Actions originated in + * Don Brown's Spring Struts Plugin. + * ContextLoaderPlugIn and DelegatingActionProxy + * constitute a clean-room implementation of the same idea, essentially + * superseding the original plugin. Many thanks to Don Brown and Matt Raible + * for the original work and for the agreement to reimplement the idea in + * Spring proper! + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see #determineActionBeanName + * @see DelegatingRequestProcessor + * @see DelegatingTilesRequestProcessor + * @see DelegatingActionUtils + * @see ContextLoaderPlugIn + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class DelegatingActionProxy extends Action { + + /** + * Pass the execute call on to the Spring-managed delegate Action. + * @see #getDelegateAction + */ + @Override + public ActionForward execute( + ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) + throws Exception { + + Action delegateAction = getDelegateAction(mapping); + return delegateAction.execute(mapping, form, request, response); + } + + + /** + * Return the delegate Action for the given mapping. + *

The default implementation determines a bean name from the + * given ActionMapping and looks up the corresponding bean in + * the {@link WebApplicationContext}. + * @param mapping the Struts ActionMapping + * @return the delegate Action + * @throws BeansException if thrown by WebApplicationContext methods + * @see #determineActionBeanName + */ + protected Action getDelegateAction(ActionMapping mapping) throws BeansException { + WebApplicationContext wac = getWebApplicationContext(getServlet(), mapping.getModuleConfig()); + String beanName = determineActionBeanName(mapping); + return (Action) wac.getBean(beanName, Action.class); + } + + /** + * Fetch ContextLoaderPlugIn's {@link WebApplicationContext} from the + * ServletContext, falling back to the root + * WebApplicationContext. + *

This context is supposed to contain the Struts Action + * beans to delegate to. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + */ + protected WebApplicationContext getWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { + + return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig); + } + + /** + * Determine the name of the Action bean, to be looked up in + * the WebApplicationContext. + *

The default implementation takes the + * {@link org.apache.struts.action.ActionMapping#getPath mapping path} and + * prepends the + * {@link org.apache.struts.config.ModuleConfig#getPrefix module prefix}, + * if any. + * @param mapping the Struts ActionMapping + * @return the name of the Action bean + * @see DelegatingActionUtils#determineActionBeanName + * @see org.apache.struts.action.ActionMapping#getPath + * @see org.apache.struts.config.ModuleConfig#getPrefix + */ + protected String determineActionBeanName(ActionMapping mapping) { + return DelegatingActionUtils.determineActionBeanName(mapping); + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionUtils.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionUtils.java new file mode 100644 index 00000000000..0fc85b091f8 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingActionUtils.java @@ -0,0 +1,212 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.config.ModuleConfig; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Common methods for letting Struts Actions work with a + * Spring WebApplicationContext. + * + *

As everything in Struts is based on concrete inheritance, + * we have to provide an Action subclass (DelegatingActionProxy) and + * two RequestProcessor subclasses (DelegatingRequestProcessor and + * DelegatingTilesRequestProcessor). The only way to share common + * functionality is a utility class like this one. + * + * @author Juergen Hoeller + * @since 1.0.2 + * @see DelegatingActionProxy + * @see DelegatingRequestProcessor + * @see DelegatingTilesRequestProcessor + * @deprecated as of Spring 3.0 + */ +@Deprecated +public abstract class DelegatingActionUtils { + + /** + * The name of the autowire init-param specified on the Struts ActionServlet: + * "spring.autowire" + */ + public static final String PARAM_AUTOWIRE = "spring.autowire"; + + /** + * The name of the dependency check init-param specified on the Struts ActionServlet: + * "spring.dependencyCheck" + */ + public static final String PARAM_DEPENDENCY_CHECK = "spring.dependencyCheck"; + + /** + * Value of the autowire init-param that indicates autowiring by name: + * "byName" + */ + public static final String AUTOWIRE_BY_NAME = "byName"; + + /** + * Value of the autowire init-param that indicates autowiring by type: + * "byType" + */ + public static final String AUTOWIRE_BY_TYPE = "byType"; + + + private static final Log logger = LogFactory.getLog(DelegatingActionUtils.class); + + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext. + *

Checks for a module-specific context first, falling back to the + * context for the default module else. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig (can be null) + * @return the WebApplicationContext, or null if none + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + */ + public static WebApplicationContext getWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) { + + WebApplicationContext wac = null; + String modulePrefix = null; + + // Try module-specific attribute. + if (moduleConfig != null) { + modulePrefix = moduleConfig.getPrefix(); + wac = (WebApplicationContext) actionServlet.getServletContext().getAttribute( + ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + modulePrefix); + } + + // If not found, try attribute for default module. + if (wac == null && !"".equals(modulePrefix)) { + wac = (WebApplicationContext) actionServlet.getServletContext().getAttribute( + ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX); + } + + return wac; + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext. + *

Checks for a module-specific context first, falling back to the + * context for the default module else. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig (can be null) + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + */ + public static WebApplicationContext getRequiredWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { + + WebApplicationContext wac = getWebApplicationContext(actionServlet, moduleConfig); + // If no Struts-specific context found, throw an exception. + if (wac == null) { + throw new IllegalStateException( + "Could not find ContextLoaderPlugIn's WebApplicationContext as ServletContext attribute [" + + ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + "]: Did you register [" + + ContextLoaderPlugIn.class.getName() + "]?"); + } + return wac; + } + + /** + * Find most specific context available: check ContextLoaderPlugIn's + * WebApplicationContext first, fall back to root WebApplicationContext else. + *

When checking the ContextLoaderPlugIn context: checks for a module-specific + * context first, falling back to the context for the default module else. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig (can be null) + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see #getWebApplicationContext + * @see org.springframework.web.context.support.WebApplicationContextUtils#getRequiredWebApplicationContext + */ + public static WebApplicationContext findRequiredWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { + + WebApplicationContext wac = getWebApplicationContext(actionServlet, moduleConfig); + // If no Struts-specific context found, fall back to root context. + if (wac == null) { + wac = WebApplicationContextUtils.getRequiredWebApplicationContext(actionServlet.getServletContext()); + } + return wac; + } + + /** + * Default implementation of Action bean determination, taking + * the mapping path and prepending the module prefix, if any. + * @param mapping the Struts ActionMapping + * @return the name of the Action bean + * @see org.apache.struts.action.ActionMapping#getPath + * @see org.apache.struts.config.ModuleConfig#getPrefix + */ + public static String determineActionBeanName(ActionMapping mapping) { + String prefix = mapping.getModuleConfig().getPrefix(); + String path = mapping.getPath(); + String beanName = prefix + path; + if (logger.isDebugEnabled()) { + logger.debug("DelegatingActionProxy with mapping path '" + path + "' and module prefix '" + + prefix + "' delegating to Spring bean with name [" + beanName + "]"); + } + return beanName; + } + + /** + * Determine the autowire mode from the "autowire" init-param of the + * Struts ActionServlet, falling back to "AUTOWIRE_BY_TYPE" as default. + * @param actionServlet the Struts ActionServlet + * @return the autowire mode to use + * @see #PARAM_AUTOWIRE + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_BY_TYPE + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_TYPE + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#AUTOWIRE_BY_NAME + */ + public static int getAutowireMode(ActionServlet actionServlet) { + String autowire = actionServlet.getInitParameter(PARAM_AUTOWIRE); + if (autowire != null) { + if (AUTOWIRE_BY_NAME.equals(autowire)) { + return AutowireCapableBeanFactory.AUTOWIRE_BY_NAME; + } + else if (!AUTOWIRE_BY_TYPE.equals(autowire)) { + throw new IllegalArgumentException("ActionServlet 'autowire' parameter must be 'byName' or 'byType'"); + } + } + return AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE; + } + + /** + * Determine the dependency check to use from the "dependencyCheck" init-param + * of the Struts ActionServlet, falling back to no dependency check as default. + * @param actionServlet the Struts ActionServlet + * @return whether to enforce a dependency check or not + * @see #PARAM_DEPENDENCY_CHECK + * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#autowireBeanProperties + */ + public static boolean getDependencyCheck(ActionServlet actionServlet) { + String dependencyCheck = actionServlet.getInitParameter(PARAM_DEPENDENCY_CHECK); + return Boolean.valueOf(dependencyCheck).booleanValue(); + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingRequestProcessor.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingRequestProcessor.java new file mode 100644 index 00000000000..9f8d647b8c6 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingRequestProcessor.java @@ -0,0 +1,201 @@ +/* + * Copyright 2002-2007 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.action.RequestProcessor; +import org.apache.struts.config.ModuleConfig; + +import org.springframework.beans.BeansException; +import org.springframework.web.context.WebApplicationContext; + +/** + * Subclass of Struts's default {@link RequestProcessor} that looks up + * Spring-managed Struts {@link Action Actions} defined in + * {@link ContextLoaderPlugIn ContextLoaderPlugIn's} {@link WebApplicationContext} + * (or, as a fallback, in the root WebApplicationContext). + * + *

In the Struts config file, you can either specify the original + * Action class (as when generated by XDoclet), or no + * Action class at all. In any case, Struts will delegate to an + * Action bean in the ContextLoaderPlugIn context. + * + *

<action path="/login" type="myapp.MyAction"/>
+ * + * or + * + *
<action path="/login"/>
+ * + * The name of the Action bean in the + * WebApplicationContext will be determined from the mapping path + * and module prefix. This can be customized by overriding the + * {@link #determineActionBeanName} method. + * + *

Example: + *

+ * + *

A corresponding bean definition in the ContextLoaderPlugin + * context would look as follows; notice that the Action is now + * able to leverage fully Spring's configuration facilities: + * + *

+ * <bean name="/login" class="myapp.MyAction">
+ *   <property name="...">...</property>
+ * </bean>
+ * + * Note that you can use a single ContextLoaderPlugIn for all + * Struts modules. That context can in turn be loaded from multiple XML files, + * for example split according to Struts modules. Alternatively, define one + * ContextLoaderPlugIn per Struts module, specifying appropriate + * "contextConfigLocation" parameters. In both cases, the Spring bean name has + * to include the module prefix. + * + *

If you also need the Tiles setup functionality of the original + * TilesRequestProcessor, use + * DelegatingTilesRequestProcessor. As there is just a + * single central class to customize in Struts, we have to provide another + * subclass here, covering both the Tiles and the Spring delegation aspect. + * + *

If this RequestProcessor conflicts with the need for a + * different RequestProcessor subclass (other than + * TilesRequestProcessor), consider using + * {@link DelegatingActionProxy} as the Struts Action type in + * your struts-config file. + * + *

The default implementation delegates to the + * DelegatingActionUtils class as much as possible, to reuse as + * much code as possible despite the need to provide two + * RequestProcessor subclasses. If you need to subclass yet + * another RequestProcessor, take this class as a template, + * delegating to DelegatingActionUtils just like it. + * + * @author Juergen Hoeller + * @since 1.0.2 + * @see #determineActionBeanName + * @see DelegatingTilesRequestProcessor + * @see DelegatingActionProxy + * @see DelegatingActionUtils + * @see ContextLoaderPlugIn + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class DelegatingRequestProcessor extends RequestProcessor { + + private WebApplicationContext webApplicationContext; + + + @Override + public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException { + super.init(actionServlet, moduleConfig); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet, moduleConfig); + } + } + + /** + * Fetch ContextLoaderPlugIn's {@link WebApplicationContext} from the + * ServletContext, falling back to the root + * WebApplicationContext. + *

This context is supposed to contain the Struts Action + * beans to delegate to. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + */ + protected WebApplicationContext initWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { + + return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig); + } + + /** + * Return the WebApplicationContext that this processor + * delegates to. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + + /** + * Override the base class method to return the delegate action. + * @see #getDelegateAction + */ + @Override + protected Action processActionCreate( + HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) + throws IOException { + + Action action = getDelegateAction(mapping); + if (action != null) { + return action; + } + return super.processActionCreate(request, response, mapping); + } + + /** + * Return the delegate Action for the given mapping. + *

The default implementation determines a bean name from the + * given ActionMapping and looks up the corresponding + * bean in the WebApplicationContext. + * @param mapping the Struts ActionMapping + * @return the delegate Action, or null if none found + * @throws BeansException if thrown by WebApplicationContext methods + * @see #determineActionBeanName + */ + protected Action getDelegateAction(ActionMapping mapping) throws BeansException { + String beanName = determineActionBeanName(mapping); + if (!getWebApplicationContext().containsBean(beanName)) { + return null; + } + return (Action) getWebApplicationContext().getBean(beanName, Action.class); + } + + /** + * Determine the name of the Action bean, to be looked up in + * the WebApplicationContext. + *

The default implementation takes the + * {@link org.apache.struts.action.ActionMapping#getPath mapping path} and + * prepends the + * {@link org.apache.struts.config.ModuleConfig#getPrefix module prefix}, + * if any. + * @param mapping the Struts ActionMapping + * @return the name of the Action bean + * @see DelegatingActionUtils#determineActionBeanName + * @see org.apache.struts.action.ActionMapping#getPath + * @see org.apache.struts.config.ModuleConfig#getPrefix + */ + protected String determineActionBeanName(ActionMapping mapping) { + return DelegatingActionUtils.determineActionBeanName(mapping); + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingTilesRequestProcessor.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingTilesRequestProcessor.java new file mode 100644 index 00000000000..9749ad53b01 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DelegatingTilesRequestProcessor.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.config.ModuleConfig; +import org.apache.struts.tiles.TilesRequestProcessor; + +import org.springframework.beans.BeansException; +import org.springframework.web.context.WebApplicationContext; + +/** + * Subclass of Struts's TilesRequestProcessor that autowires + * Struts Actions defined in ContextLoaderPlugIn's WebApplicationContext + * (or, as a fallback, in the root WebApplicationContext). + * + *

Behaves like + * {@link DelegatingRequestProcessor DelegatingRequestProcessor}, + * but also provides the Tiles functionality of the original TilesRequestProcessor. + * As there's just a single central class to customize in Struts, we have to provide + * another subclass here, covering both the Tiles and the Spring delegation aspect. + * + *

The default implementation delegates to the DelegatingActionUtils + * class as fas as possible, to reuse as much code as possible despite + * the need to provide two RequestProcessor subclasses. If you need to + * subclass yet another RequestProcessor, take this class as a template, + * delegating to DelegatingActionUtils just like it. + * + * @author Juergen Hoeller + * @since 1.0.2 + * @see DelegatingRequestProcessor + * @see DelegatingActionProxy + * @see DelegatingActionUtils + * @see ContextLoaderPlugIn + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class DelegatingTilesRequestProcessor extends TilesRequestProcessor { + + private WebApplicationContext webApplicationContext; + + + @Override + public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException { + super.init(actionServlet, moduleConfig); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet, moduleConfig); + } + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext, + * falling back to the root WebApplicationContext. This context is supposed + * to contain the Struts Action beans to delegate to. + * @param actionServlet the associated ActionServlet + * @param moduleConfig the associated ModuleConfig + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + */ + protected WebApplicationContext initWebApplicationContext( + ActionServlet actionServlet, ModuleConfig moduleConfig) throws IllegalStateException { + + return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, moduleConfig); + } + + /** + * Return the WebApplicationContext that this processor delegates to. + */ + protected final WebApplicationContext getWebApplicationContext() { + return webApplicationContext; + } + + + /** + * Override the base class method to return the delegate action. + * @see #getDelegateAction + */ + @Override + protected Action processActionCreate( + HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) + throws IOException { + + Action action = getDelegateAction(mapping); + if (action != null) { + return action; + } + return super.processActionCreate(request, response, mapping); + } + + /** + * Return the delegate Action for the given mapping. + *

The default implementation determines a bean name from the + * given ActionMapping and looks up the corresponding bean in the + * WebApplicationContext. + * @param mapping the Struts ActionMapping + * @return the delegate Action, or null if none found + * @throws BeansException if thrown by WebApplicationContext methods + * @see #determineActionBeanName + */ + protected Action getDelegateAction(ActionMapping mapping) throws BeansException { + String beanName = determineActionBeanName(mapping); + if (!getWebApplicationContext().containsBean(beanName)) { + return null; + } + return (Action) getWebApplicationContext().getBean(beanName, Action.class); + } + + /** + * Determine the name of the Action bean, to be looked up in + * the WebApplicationContext. + *

The default implementation takes the mapping path and + * prepends the module prefix, if any. + * @param mapping the Struts ActionMapping + * @return the name of the Action bean + * @see DelegatingActionUtils#determineActionBeanName + * @see ActionMapping#getPath + * @see ModuleConfig#getPrefix + */ + protected String determineActionBeanName(ActionMapping mapping) { + return DelegatingActionUtils.determineActionBeanName(mapping); + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DispatchActionSupport.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DispatchActionSupport.java new file mode 100644 index 00000000000..bb74fafa1c9 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/DispatchActionSupport.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.File; + +import javax.servlet.ServletContext; + +import org.apache.struts.action.ActionServlet; +import org.apache.struts.actions.DispatchAction; + +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.util.WebUtils; + +/** + * Convenience class for Spring-aware Struts 1.1+ DispatchActions. + * + *

Provides a reference to the current Spring application context, e.g. + * for bean lookup or resource loading. Auto-detects a ContextLoaderPlugIn + * context, falling back to the root WebApplicationContext. For typical + * usage, i.e. accessing middle tier beans, use a root WebApplicationContext. + * + *

For classic Struts Actions or Lookup/MappingDispatchActions, use the + * analogous {@link ActionSupport ActionSupport} or + * {@link LookupDispatchActionSupport LookupDispatchActionSupport} / + * {@link MappingDispatchActionSupport MappingDispatchActionSupport} class, + * respectively. + * + *

As an alternative approach, you can wire your Struts Actions themselves + * as Spring beans, passing references to them via IoC rather than looking + * up references in a programmatic fashion. Check out + * {@link DelegatingActionProxy DelegatingActionProxy} and + * {@link DelegatingRequestProcessor DelegatingRequestProcessor}. + * + * @author Juergen Hoeller + * @since 1.0.1 + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + * @see org.springframework.web.context.ContextLoaderListener + * @see org.springframework.web.context.ContextLoaderServlet + * @see ActionSupport + * @see LookupDispatchActionSupport + * @see MappingDispatchActionSupport + * @see DelegatingActionProxy + * @see DelegatingRequestProcessor + * @deprecated as of Spring 3.0 + */ +@Deprecated +public abstract class DispatchActionSupport extends DispatchAction { + + private WebApplicationContext webApplicationContext; + + private MessageSourceAccessor messageSourceAccessor; + + + /** + * Initialize the WebApplicationContext for this Action. + * Invokes onInit after successful initialization of the context. + * @see #initWebApplicationContext + * @see #onInit + */ + @Override + public void setServlet(ActionServlet actionServlet) { + super.setServlet(actionServlet); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet); + this.messageSourceAccessor = new MessageSourceAccessor(this.webApplicationContext); + onInit(); + } + else { + onDestroy(); + } + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext, + * falling back to the root WebApplicationContext (the usual case). + * @param actionServlet the associated ActionServlet + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + */ + protected WebApplicationContext initWebApplicationContext(ActionServlet actionServlet) + throws IllegalStateException { + + return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, null); + } + + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return a MessageSourceAccessor for the application context + * used by this object, for easy message access. + */ + protected final MessageSourceAccessor getMessageSourceAccessor() { + return this.messageSourceAccessor; + } + + /** + * Return the current ServletContext. + */ + protected final ServletContext getServletContext() { + return this.webApplicationContext.getServletContext(); + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @return the File representing the temporary directory + */ + protected final File getTempDir() { + return WebUtils.getTempDir(getServletContext()); + } + + + /** + * Callback for custom initialization after the context has been set up. + * @see #setServlet + */ + protected void onInit() { + } + + /** + * Callback for custom destruction when the ActionServlet shuts down. + * @see #setServlet + */ + protected void onDestroy() { + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/LookupDispatchActionSupport.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/LookupDispatchActionSupport.java new file mode 100644 index 00000000000..9c606b91b34 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/LookupDispatchActionSupport.java @@ -0,0 +1,150 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.File; + +import javax.servlet.ServletContext; + +import org.apache.struts.action.ActionServlet; +import org.apache.struts.actions.LookupDispatchAction; + +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.util.WebUtils; + +/** + * Convenience class for Spring-aware Struts 1.1+ LookupDispatchActions. + * + *

Provides a reference to the current Spring application context, e.g. + * for bean lookup or resource loading. Auto-detects a ContextLoaderPlugIn + * context, falling back to the root WebApplicationContext. For typical + * usage, i.e. accessing middle tier beans, use a root WebApplicationContext. + * + *

For classic Struts Actions, DispatchActions or MappingDispatchActions, + * use the analogous {@link ActionSupport ActionSupport} or + * {@link DispatchActionSupport DispatchActionSupport} / + * {@link MappingDispatchActionSupport MappingDispatchActionSupport} class. + * + *

As an alternative approach, you can wire your Struts Actions themselves + * as Spring beans, passing references to them via IoC rather than looking + * up references in a programmatic fashion. Check out + * {@link DelegatingActionProxy DelegatingActionProxy} and + * {@link DelegatingRequestProcessor DelegatingRequestProcessor}. + * + * @author Juergen Hoeller + * @since 1.1 + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + * @see WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + * @see org.springframework.web.context.ContextLoaderListener + * @see org.springframework.web.context.ContextLoaderServlet + * @see ActionSupport + * @see DispatchActionSupport + * @see MappingDispatchActionSupport + * @see DelegatingActionProxy + * @see DelegatingRequestProcessor + * @deprecated as of Spring 3.0 + */ +@Deprecated +public abstract class LookupDispatchActionSupport extends LookupDispatchAction { + + private WebApplicationContext webApplicationContext; + + private MessageSourceAccessor messageSourceAccessor; + + + /** + * Initialize the WebApplicationContext for this Action. + * Invokes onInit after successful initialization of the context. + * @see #initWebApplicationContext + * @see #onInit + */ + @Override + public void setServlet(ActionServlet actionServlet) { + super.setServlet(actionServlet); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet); + this.messageSourceAccessor = new MessageSourceAccessor(this.webApplicationContext); + onInit(); + } + else { + onDestroy(); + } + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext, + * falling back to the root WebApplicationContext (the usual case). + * @param actionServlet the associated ActionServlet + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + */ + protected WebApplicationContext initWebApplicationContext(ActionServlet actionServlet) + throws IllegalStateException { + + return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, null); + } + + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return a MessageSourceAccessor for the application context + * used by this object, for easy message access. + */ + protected final MessageSourceAccessor getMessageSourceAccessor() { + return this.messageSourceAccessor; + } + + /** + * Return the current ServletContext. + */ + protected final ServletContext getServletContext() { + return this.webApplicationContext.getServletContext(); + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @return the File representing the temporary directory + */ + protected final File getTempDir() { + return WebUtils.getTempDir(getServletContext()); + } + + + /** + * Callback for custom initialization after the context has been set up. + * @see #setServlet + */ + protected void onInit() { + } + + /** + * Callback for custom destruction when the ActionServlet shuts down. + * @see #setServlet + */ + protected void onDestroy() { + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/MappingDispatchActionSupport.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/MappingDispatchActionSupport.java new file mode 100644 index 00000000000..ec08c10771c --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/MappingDispatchActionSupport.java @@ -0,0 +1,150 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.io.File; + +import javax.servlet.ServletContext; + +import org.apache.struts.action.ActionServlet; +import org.apache.struts.actions.MappingDispatchAction; + +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.util.WebUtils; + +/** + * Convenience class for Spring-aware Struts 1.2 MappingDispatchActions. + * + *

Provides a reference to the current Spring application context, e.g. + * for bean lookup or resource loading. Auto-detects a ContextLoaderPlugIn + * context, falling back to the root WebApplicationContext. For typical + * usage, i.e. accessing middle tier beans, use a root WebApplicationContext. + * + *

For classic Struts Actions, DispatchActions or LookupDispatchActions, + * use the analogous {@link ActionSupport ActionSupport} or + * {@link DispatchActionSupport DispatchActionSupport} / + * {@link LookupDispatchActionSupport LookupDispatchActionSupport} class. + * + *

As an alternative approach, you can wire your Struts Actions themselves + * as Spring beans, passing references to them via IoC rather than looking + * up references in a programmatic fashion. Check out + * {@link DelegatingActionProxy DelegatingActionProxy} and + * {@link DelegatingRequestProcessor DelegatingRequestProcessor}. + * + * @author Juergen Hoeller + * @since 1.1.3 + * @see ContextLoaderPlugIn#SERVLET_CONTEXT_PREFIX + * @see WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + * @see org.springframework.web.context.ContextLoaderListener + * @see org.springframework.web.context.ContextLoaderServlet + * @see ActionSupport + * @see DispatchActionSupport + * @see LookupDispatchActionSupport + * @see DelegatingActionProxy + * @see DelegatingRequestProcessor + * @deprecated as of Spring 3.0 + */ +@Deprecated +public abstract class MappingDispatchActionSupport extends MappingDispatchAction { + + private WebApplicationContext webApplicationContext; + + private MessageSourceAccessor messageSourceAccessor; + + + /** + * Initialize the WebApplicationContext for this Action. + * Invokes onInit after successful initialization of the context. + * @see #initWebApplicationContext + * @see #onInit + */ + @Override + public void setServlet(ActionServlet actionServlet) { + super.setServlet(actionServlet); + if (actionServlet != null) { + this.webApplicationContext = initWebApplicationContext(actionServlet); + this.messageSourceAccessor = new MessageSourceAccessor(this.webApplicationContext); + onInit(); + } + else { + onDestroy(); + } + } + + /** + * Fetch ContextLoaderPlugIn's WebApplicationContext from the ServletContext, + * falling back to the root WebApplicationContext (the usual case). + * @param actionServlet the associated ActionServlet + * @return the WebApplicationContext + * @throws IllegalStateException if no WebApplicationContext could be found + * @see DelegatingActionUtils#findRequiredWebApplicationContext + */ + protected WebApplicationContext initWebApplicationContext(ActionServlet actionServlet) + throws IllegalStateException { + + return DelegatingActionUtils.findRequiredWebApplicationContext(actionServlet, null); + } + + + /** + * Return the current Spring WebApplicationContext. + */ + protected final WebApplicationContext getWebApplicationContext() { + return this.webApplicationContext; + } + + /** + * Return a MessageSourceAccessor for the application context + * used by this object, for easy message access. + */ + protected final MessageSourceAccessor getMessageSourceAccessor() { + return this.messageSourceAccessor; + } + + /** + * Return the current ServletContext. + */ + protected final ServletContext getServletContext() { + return this.webApplicationContext.getServletContext(); + } + + /** + * Return the temporary directory for the current web application, + * as provided by the servlet container. + * @return the File representing the temporary directory + */ + protected final File getTempDir() { + return WebUtils.getTempDir(getServletContext()); + } + + + /** + * Callback for custom initialization after the context has been set up. + * @see #setServlet + */ + protected void onInit() { + } + + /** + * Callback for custom destruction when the ActionServlet shuts down. + * @see #setServlet + */ + protected void onDestroy() { + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/SpringBindingActionForm.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/SpringBindingActionForm.java new file mode 100644 index 00000000000..c16f719c626 --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/SpringBindingActionForm.java @@ -0,0 +1,288 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.lang.reflect.InvocationTargetException; +import java.util.Iterator; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.beanutils.BeanUtilsBean; +import org.apache.commons.beanutils.ConvertUtilsBean; +import org.apache.commons.beanutils.PropertyUtilsBean; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.struts.Globals; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionMessage; +import org.apache.struts.action.ActionMessages; +import org.apache.struts.util.MessageResources; + +import org.springframework.context.MessageSourceResolvable; +import org.springframework.validation.Errors; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; + +/** + * A thin Struts ActionForm adapter that delegates to Spring's more complete + * and advanced data binder and Errors object underneath the covers to bind + * to POJOs and manage rejected values. + * + *

Exposes Spring-managed errors to the standard Struts view tags, through + * exposing a corresponding Struts ActionMessages object as request attribute. + * Also exposes current field values in a Struts-compliant fashion, including + * rejected values (which Spring's binding keeps even for non-String fields). + * + *

Consequently, Struts views can be written in a completely traditional + * fashion (with standard html:form, html:errors, etc), + * seamlessly accessing a Spring-bound POJO form object underneath. + * + *

Note this ActionForm is designed explicitly for use in request scope. + * It expects to receive an expose call from the Action, passing + * in the Errors object to expose plus the current HttpServletRequest. + * + *

Example definition in struts-config.xml: + * + *

+ * <form-beans>
+ *   <form-bean name="actionForm" type="org.springframework.web.struts.SpringBindingActionForm"/>
+ * </form-beans>
+ * + * Example code in a custom Struts Action: + * + *
+ * public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
+ *   SpringBindingActionForm form = (SpringBindingActionForm) actionForm;
+ *   MyPojoBean bean = ...;
+ *   ServletRequestDataBinder binder = new ServletRequestDataBinder(bean, "myPojo");
+ *   binder.bind(request);
+ *   form.expose(binder.getBindingResult(), request);
+ *   return actionMapping.findForward("success");
+ * }
+ * + * This class is compatible with both Struts 1.2.x and Struts 1.1. + * On Struts 1.2, default messages registered with Spring binding errors + * are exposed when none of the error codes could be resolved. + * On Struts 1.1, this is not possible due to a limitation in the Struts + * message facility; hence, we expose the plain default error code there. + * + * @author Keith Donald + * @author Juergen Hoeller + * @since 1.2.2 + * @see #expose(org.springframework.validation.Errors, javax.servlet.http.HttpServletRequest) + * @deprecated as of Spring 3.0 + */ +@Deprecated +public class SpringBindingActionForm extends ActionForm { + + private static final Log logger = LogFactory.getLog(SpringBindingActionForm.class); + + private static boolean defaultActionMessageAvailable = true; + + + static { + // Register special PropertyUtilsBean subclass that knows how to + // extract field values from a SpringBindingActionForm. + // As a consequence of the static nature of Commons BeanUtils, + // we have to resort to this initialization hack here. + ConvertUtilsBean convUtils = new ConvertUtilsBean(); + PropertyUtilsBean propUtils = new SpringBindingAwarePropertyUtilsBean(); + BeanUtilsBean beanUtils = new BeanUtilsBean(convUtils, propUtils); + BeanUtilsBean.setInstance(beanUtils); + + // Determine whether the Struts 1.2 support for default messages + // is available on ActionMessage: ActionMessage(String, boolean) + // with "false" to be passed into the boolean flag. + try { + ActionMessage.class.getConstructor(new Class[] {String.class, boolean.class}); + } + catch (NoSuchMethodException ex) { + defaultActionMessageAvailable = false; + } + } + + + private Errors errors; + + private Locale locale; + + private MessageResources messageResources; + + + /** + * Set the Errors object that this SpringBindingActionForm is supposed + * to wrap. The contained field values and errors will be exposed + * to the view, accessible through Struts standard tags. + * @param errors the Spring Errors object to wrap, usually taken from + * a DataBinder that has been used for populating a POJO form object + * @param request the HttpServletRequest to retrieve the attributes from + * @see org.springframework.validation.DataBinder#getBindingResult() + */ + public void expose(Errors errors, HttpServletRequest request) { + this.errors = errors; + + // Obtain the locale from Struts well-known location. + this.locale = (Locale) request.getSession().getAttribute(Globals.LOCALE_KEY); + + // Obtain the MessageResources from Struts' well-known location. + this.messageResources = (MessageResources) request.getAttribute(Globals.MESSAGES_KEY); + + if (errors != null && errors.hasErrors()) { + // Add global ActionError instances from the Spring Errors object. + ActionMessages actionMessages = (ActionMessages) request.getAttribute(Globals.ERROR_KEY); + if (actionMessages == null) { + request.setAttribute(Globals.ERROR_KEY, getActionMessages()); + } + else { + actionMessages.add(getActionMessages()); + } + } + } + + + /** + * Return an ActionMessages representation of this SpringBindingActionForm, + * exposing all errors contained in the underlying Spring Errors object. + * @see org.springframework.validation.Errors#getAllErrors() + */ + private ActionMessages getActionMessages() { + ActionMessages actionMessages = new ActionMessages(); + Iterator it = this.errors.getAllErrors().iterator(); + while (it.hasNext()) { + ObjectError objectError = (ObjectError) it.next(); + String effectiveMessageKey = findEffectiveMessageKey(objectError); + if (effectiveMessageKey == null && !defaultActionMessageAvailable) { + // Need to specify default code despite it not being resolvable: + // Struts 1.1 ActionMessage doesn't support default messages. + effectiveMessageKey = objectError.getCode(); + } + ActionMessage message = (effectiveMessageKey != null) ? + new ActionMessage(effectiveMessageKey, resolveArguments(objectError.getArguments())) : + new ActionMessage(objectError.getDefaultMessage(), false); + if (objectError instanceof FieldError) { + FieldError fieldError = (FieldError) objectError; + actionMessages.add(fieldError.getField(), message); + } + else { + actionMessages.add(ActionMessages.GLOBAL_MESSAGE, message); + } + } + if (logger.isDebugEnabled()) { + logger.debug("Final ActionMessages used for binding: " + actionMessages); + } + return actionMessages; + } + + private Object[] resolveArguments(Object[] arguments) { + if (arguments == null || arguments.length == 0) { + return arguments; + } + for (int i = 0; i < arguments.length; i++) { + Object arg = arguments[i]; + if (arg instanceof MessageSourceResolvable) { + MessageSourceResolvable resolvable = (MessageSourceResolvable)arg; + String[] codes = resolvable.getCodes(); + boolean resolved = false; + if (this.messageResources != null) { + for (int j = 0; j < codes.length; j++) { + String code = codes[j]; + if (this.messageResources.isPresent(this.locale, code)) { + arguments[i] = this.messageResources.getMessage( + this.locale, code, resolveArguments(resolvable.getArguments())); + resolved = true; + break; + } + } + } + if (!resolved) { + arguments[i] = resolvable.getDefaultMessage(); + } + } + } + return arguments; + } + + /** + * Find the most specific message key for the given error. + * @param error the ObjectError to find a message key for + * @return the most specific message key found + */ + private String findEffectiveMessageKey(ObjectError error) { + if (this.messageResources != null) { + String[] possibleMatches = error.getCodes(); + for (int i = 0; i < possibleMatches.length; i++) { + if (logger.isDebugEnabled()) { + logger.debug("Looking for error code '" + possibleMatches[i] + "'"); + } + if (this.messageResources.isPresent(this.locale, possibleMatches[i])) { + if (logger.isDebugEnabled()) { + logger.debug("Found error code '" + possibleMatches[i] + "' in resource bundle"); + } + return possibleMatches[i]; + } + } + } + if (logger.isDebugEnabled()) { + logger.debug("Could not find a suitable message error code, returning default message"); + } + return null; + } + + + /** + * Get the formatted value for the property at the provided path. + * The formatted value is a string value for display, converted + * via a registered property editor. + * @param propertyPath the property path + * @return the formatted property value + * @throws NoSuchMethodException if called during Struts binding + * (without Spring Errors object being exposed), to indicate no + * available property to Struts + */ + private Object getFieldValue(String propertyPath) throws NoSuchMethodException { + if (this.errors == null) { + throw new NoSuchMethodException( + "No bean properties exposed to Struts binding - performing Spring binding later on"); + } + return this.errors.getFieldValue(propertyPath); + } + + + /** + * Special subclass of PropertyUtilsBean that it is aware of SpringBindingActionForm + * and uses it for retrieving field values. The field values will be taken from + * the underlying POJO form object that the Spring Errors object was created for. + */ + private static class SpringBindingAwarePropertyUtilsBean extends PropertyUtilsBean { + + @Override + public Object getNestedProperty(Object bean, String propertyPath) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + + // Extract Spring-managed field value in case of SpringBindingActionForm. + if (bean instanceof SpringBindingActionForm) { + SpringBindingActionForm form = (SpringBindingActionForm) bean; + return form.getFieldValue(propertyPath); + } + + // Else fall back to default PropertyUtils behavior. + return super.getNestedProperty(bean, propertyPath); + } + } + +} diff --git a/org.springframework.web.struts/src/main/java/org/springframework/web/struts/package-info.java b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/package-info.java new file mode 100644 index 00000000000..b299f31263d --- /dev/null +++ b/org.springframework.web.struts/src/main/java/org/springframework/web/struts/package-info.java @@ -0,0 +1,28 @@ +/** + * Support classes for integrating a Struts web tier with a Spring middle + * tier which is typically hosted in a Spring root WebApplicationContext. + * + *

Supports easy access to the Spring root WebApplicationContext + * from Struts Actions via the ActionSupport and DispatchActionSupport + * classes. Actions have full access to Spring's WebApplicationContext + * facilities in this case, and explicitly look up Spring-managed beans. + * + *

Also supports wiring Struts Actions as Spring-managed beans in + * a ContextLoaderPlugIn context, passing middle tier references to them + * via bean references, using the Action path as bean name. There are two + * ways to make Struts delegate Action lookup to the ContextLoaderPlugIn: + * + *

+ */ +@Deprecated +package org.springframework.web.struts; \ No newline at end of file diff --git a/org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TestComponentController.java b/org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TestComponentController.java new file mode 100644 index 00000000000..d65b6c7a4dc --- /dev/null +++ b/org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TestComponentController.java @@ -0,0 +1,36 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed 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.springframework.web.servlet.view.tiles; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.tiles.ComponentContext; + +/** + * @author Juergen Hoeller + * @since 22.08.2003 + */ +public class TestComponentController extends ComponentControllerSupport { + + @Override + protected void doPerform(ComponentContext componentContext, HttpServletRequest request, HttpServletResponse response) { + request.setAttribute("testAttr", "testVal"); + TilesView.setPath(request, "/WEB-INF/jsp/layout.jsp"); + } + +} diff --git a/org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TilesViewTests.java b/org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TilesViewTests.java new file mode 100644 index 00000000000..389c18f9b37 --- /dev/null +++ b/org.springframework.web.struts/src/test/java/org/springframework/web/servlet/view/tiles/TilesViewTests.java @@ -0,0 +1,181 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed 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.springframework.web.servlet.view.tiles; + +import java.util.HashMap; +import java.util.Locale; +import javax.servlet.jsp.jstl.core.Config; +import javax.servlet.jsp.jstl.fmt.LocalizationContext; + +import org.apache.struts.taglib.tiles.ComponentConstants; +import org.apache.struts.tiles.ComponentContext; +import org.apache.struts.tiles.PathAttribute; +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; +import org.springframework.web.servlet.i18n.FixedLocaleResolver; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + +/** + * @author Alef Arendsen + * @author Juergen Hoeller + */ +public class TilesViewTests { + + protected StaticWebApplicationContext prepareWebApplicationContext() throws Exception { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + MockServletContext sc = new MockServletContext("/org/springframework/web/servlet/view/tiles/"); + wac.setServletContext(sc); + wac.refresh(); + + TilesConfigurer tc = new TilesConfigurer(); + tc.setDefinitions(new String[] {"tiles-test.xml"}); + tc.setValidateDefinitions(true); + tc.setApplicationContext(wac); + tc.afterPropertiesSet(); + + return wac; + } + + @Test + public void tilesView() throws Exception { + WebApplicationContext wac = prepareWebApplicationContext(); + + InternalResourceViewResolver irvr = new InternalResourceViewResolver(); + irvr.setApplicationContext(wac); + irvr.setViewClass(TilesView.class); + View view = irvr.resolveViewName("testTile", new Locale("nl", "")); + + MockHttpServletRequest request = new MockHttpServletRequest(wac.getServletContext()); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + request.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, new AcceptHeaderLocaleResolver()); + + view.render(new HashMap(), request, response); + assertEquals("/WEB-INF/jsp/layout.jsp", response.getForwardedUrl()); + ComponentContext cc = (ComponentContext) request.getAttribute(ComponentConstants.COMPONENT_CONTEXT); + assertNotNull(cc); + PathAttribute attr = (PathAttribute) cc.getAttribute("content"); + assertEquals("/WEB-INF/jsp/content.jsp", attr.getValue()); + + view.render(new HashMap(), request, response); + assertEquals("/WEB-INF/jsp/layout.jsp", response.getForwardedUrl()); + cc = (ComponentContext) request.getAttribute(ComponentConstants.COMPONENT_CONTEXT); + assertNotNull(cc); + attr = (PathAttribute) cc.getAttribute("content"); + assertEquals("/WEB-INF/jsp/content.jsp", attr.getValue()); + } + + @Test + public void tilesJstlView() throws Exception { + Locale locale = !Locale.GERMAN.equals(Locale.getDefault()) ? Locale.GERMAN : Locale.FRENCH; + + StaticWebApplicationContext wac = prepareWebApplicationContext(); + + InternalResourceViewResolver irvr = new InternalResourceViewResolver(); + irvr.setApplicationContext(wac); + irvr.setViewClass(TilesJstlView.class); + View view = irvr.resolveViewName("testTile", new Locale("nl", "")); + + MockHttpServletRequest request = new MockHttpServletRequest(wac.getServletContext()); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + request.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, new FixedLocaleResolver(locale)); + wac.addMessage("code1", locale, "messageX"); + view.render(new HashMap(), request, response); + + assertEquals("/WEB-INF/jsp/layout.jsp", response.getForwardedUrl()); + ComponentContext cc = (ComponentContext) request.getAttribute(ComponentConstants.COMPONENT_CONTEXT); + assertNotNull(cc); + PathAttribute attr = (PathAttribute) cc.getAttribute("content"); + assertEquals("/WEB-INF/jsp/content.jsp", attr.getValue()); + + assertEquals(locale, Config.get(request, Config.FMT_LOCALE)); + LocalizationContext lc = (LocalizationContext) Config.get(request, Config.FMT_LOCALIZATION_CONTEXT); + assertEquals("messageX", lc.getResourceBundle().getString("code1")); + } + + @Test + public void tilesJstlViewWithContextParam() throws Exception { + Locale locale = !Locale.GERMAN.equals(Locale.getDefault()) ? Locale.GERMAN : Locale.FRENCH; + + StaticWebApplicationContext wac = prepareWebApplicationContext(); + ((MockServletContext) wac.getServletContext()).addInitParameter( + Config.FMT_LOCALIZATION_CONTEXT, "org/springframework/web/servlet/view/tiles/context-messages"); + + InternalResourceViewResolver irvr = new InternalResourceViewResolver(); + irvr.setApplicationContext(wac); + irvr.setViewClass(TilesJstlView.class); + View view = irvr.resolveViewName("testTile", new Locale("nl", "")); + + MockHttpServletRequest request = new MockHttpServletRequest(wac.getServletContext()); + MockHttpServletResponse response = new MockHttpServletResponse(); + wac.addMessage("code1", locale, "messageX"); + request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + request.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, new FixedLocaleResolver(locale)); + + view.render(new HashMap(), request, response); + assertEquals("/WEB-INF/jsp/layout.jsp", response.getForwardedUrl()); + ComponentContext cc = (ComponentContext) request.getAttribute(ComponentConstants.COMPONENT_CONTEXT); + assertNotNull(cc); + PathAttribute attr = (PathAttribute) cc.getAttribute("content"); + assertEquals("/WEB-INF/jsp/content.jsp", attr.getValue()); + + LocalizationContext lc = (LocalizationContext) Config.get(request, Config.FMT_LOCALIZATION_CONTEXT); + assertEquals("message1", lc.getResourceBundle().getString("code1")); + assertEquals("message2", lc.getResourceBundle().getString("code2")); + } + + @Test + public void tilesViewWithController() throws Exception { + WebApplicationContext wac = prepareWebApplicationContext(); + + InternalResourceViewResolver irvr = new InternalResourceViewResolver(); + irvr.setApplicationContext(wac); + irvr.setViewClass(TilesView.class); + View view = irvr.resolveViewName("testTileWithController", new Locale("nl", "")); + + MockHttpServletRequest request = new MockHttpServletRequest(wac.getServletContext()); + MockHttpServletResponse response = new MockHttpServletResponse(); + request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + request.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, new AcceptHeaderLocaleResolver()); + view.render(new HashMap(), request, response); + assertEquals("/WEB-INF/jsp/layout.jsp", response.getForwardedUrl()); + ComponentContext cc = (ComponentContext) request.getAttribute(ComponentConstants.COMPONENT_CONTEXT); + assertNotNull(cc); + PathAttribute attr = (PathAttribute) cc.getAttribute("content"); + assertEquals("/WEB-INF/jsp/otherContent.jsp", attr.getValue()); + assertEquals("testVal", request.getAttribute("testAttr")); + + view.render(new HashMap(), request, response); + assertEquals("/WEB-INF/jsp/layout.jsp", response.getForwardedUrl()); + cc = (ComponentContext) request.getAttribute(ComponentConstants.COMPONENT_CONTEXT); + assertNotNull(cc); + attr = (PathAttribute) cc.getAttribute("content"); + assertEquals("/WEB-INF/jsp/otherContent.jsp", attr.getValue()); + assertEquals("testVal", request.getAttribute("testAttr")); + } + +} diff --git a/org.springframework.web.struts/src/test/java/org/springframework/web/struts/StrutsSupportTests.java b/org.springframework.web.struts/src/test/java/org/springframework/web/struts/StrutsSupportTests.java new file mode 100644 index 00000000000..93394707022 --- /dev/null +++ b/org.springframework.web.struts/src/test/java/org/springframework/web/struts/StrutsSupportTests.java @@ -0,0 +1,337 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.apache.struts.action.ActionServlet; +import org.apache.struts.config.ModuleConfig; +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StaticWebApplicationContext; + +/** + * @author Juergen Hoeller + * @since 09.04.2004 + */ +public class StrutsSupportTests { + + @Test + public void actionSupportWithContextLoaderPlugIn() throws ServletException { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.addMessage("test", Locale.getDefault(), "testmessage"); + final ServletContext servletContext = new MockServletContext(); + wac.setServletContext(servletContext); + wac.refresh(); + servletContext.setAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX, wac); + + ActionServlet actionServlet = new ActionServlet() { + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + ActionSupport action = new ActionSupport() { + }; + action.setServlet(actionServlet); + + assertEquals(wac, action.getWebApplicationContext()); + assertEquals(servletContext, action.getServletContext()); + assertEquals("testmessage", action.getMessageSourceAccessor().getMessage("test")); + + action.setServlet(null); + } + + @Test + public void actionSupportWithRootContext() throws ServletException { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.addMessage("test", Locale.getDefault(), "testmessage"); + final ServletContext servletContext = new MockServletContext(); + wac.setServletContext(servletContext); + wac.refresh(); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + + ActionServlet actionServlet = new ActionServlet() { + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + ActionSupport action = new ActionSupport() { + }; + action.setServlet(actionServlet); + + assertEquals(wac, action.getWebApplicationContext()); + assertEquals(servletContext, action.getServletContext()); + assertEquals("testmessage", action.getMessageSourceAccessor().getMessage("test")); + + action.setServlet(null); + } + + @Test + public void dispatchActionSupportWithContextLoaderPlugIn() throws ServletException { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.addMessage("test", Locale.getDefault(), "testmessage"); + final ServletContext servletContext = new MockServletContext(); + wac.setServletContext(servletContext); + wac.refresh(); + servletContext.setAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX, wac); + + ActionServlet actionServlet = new ActionServlet() { + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + DispatchActionSupport action = new DispatchActionSupport() { + }; + action.setServlet(actionServlet); + + assertEquals(wac, action.getWebApplicationContext()); + assertEquals(servletContext, action.getServletContext()); + assertEquals("testmessage", action.getMessageSourceAccessor().getMessage("test")); + + action.setServlet(null); + } + + @Test + public void dispatchActionSupportWithRootContext() throws ServletException { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.addMessage("test", Locale.getDefault(), "testmessage"); + final ServletContext servletContext = new MockServletContext(); + wac.setServletContext(servletContext); + wac.refresh(); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + + ActionServlet actionServlet = new ActionServlet() { + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + DispatchActionSupport action = new DispatchActionSupport() { + }; + action.setServlet(actionServlet); + + assertEquals(wac, action.getWebApplicationContext()); + assertEquals(servletContext, action.getServletContext()); + assertEquals("testmessage", action.getMessageSourceAccessor().getMessage("test")); + + action.setServlet(null); + } + + @Test + public void lookupDispatchActionSupportWithContextLoaderPlugIn() throws ServletException { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.addMessage("test", Locale.getDefault(), "testmessage"); + final ServletContext servletContext = new MockServletContext(); + wac.setServletContext(servletContext); + wac.refresh(); + servletContext.setAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX, wac); + + ActionServlet actionServlet = new ActionServlet() { + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + LookupDispatchActionSupport action = new LookupDispatchActionSupport() { + @Override + protected Map getKeyMethodMap() { + return new HashMap(); + } + }; + action.setServlet(actionServlet); + + assertEquals(wac, action.getWebApplicationContext()); + assertEquals(servletContext, action.getServletContext()); + assertEquals("testmessage", action.getMessageSourceAccessor().getMessage("test")); + + action.setServlet(null); + } + + @Test + public void lookupDispatchActionSupportWithRootContext() throws ServletException { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.addMessage("test", Locale.getDefault(), "testmessage"); + final ServletContext servletContext = new MockServletContext(); + wac.setServletContext(servletContext); + wac.refresh(); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac); + + ActionServlet actionServlet = new ActionServlet() { + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + LookupDispatchActionSupport action = new LookupDispatchActionSupport() { + @Override + protected Map getKeyMethodMap() { + return new HashMap(); + } + }; + action.setServlet(actionServlet); + + assertEquals(wac, action.getWebApplicationContext()); + assertEquals(servletContext, action.getServletContext()); + assertEquals("testmessage", action.getMessageSourceAccessor().getMessage("test")); + + action.setServlet(null); + } + + @Test + public void testDelegatingActionProxy() throws Exception { + final MockServletContext servletContext = new MockServletContext("/org/springframework/web/struts/"); + ContextLoaderPlugIn plugin = new ContextLoaderPlugIn(); + ActionServlet actionServlet = new ActionServlet() { + @Override + public String getServletName() { + return "action"; + } + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + + ModuleConfig moduleConfig = createMock(ModuleConfig.class); + expect(moduleConfig.getPrefix()).andReturn("").anyTimes(); + replay(moduleConfig); + + plugin.init(actionServlet, moduleConfig); + assertTrue(servletContext.getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX) != null); + + DelegatingActionProxy proxy = new DelegatingActionProxy(); + proxy.setServlet(actionServlet); + ActionMapping mapping = new ActionMapping(); + mapping.setPath("/test"); + mapping.setModuleConfig(moduleConfig); + ActionForward forward = proxy.execute( + mapping, null, new MockHttpServletRequest(servletContext), new MockHttpServletResponse()); + assertEquals("/test", forward.getPath()); + + TestAction testAction = (TestAction) plugin.getWebApplicationContext().getBean("/test"); + assertTrue(testAction.getServlet() != null); + proxy.setServlet(null); + plugin.destroy(); + assertTrue(testAction.getServlet() == null); + + verify(moduleConfig); + } + + @Test + public void delegatingActionProxyWithModule() throws Exception { + final MockServletContext servletContext = new MockServletContext("/org/springframework/web/struts/WEB-INF"); + ContextLoaderPlugIn plugin = new ContextLoaderPlugIn(); + plugin.setContextConfigLocation("action-servlet.xml"); + ActionServlet actionServlet = new ActionServlet() { + @Override + public String getServletName() { + return "action"; + } + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + + ModuleConfig moduleConfig = createMock(ModuleConfig.class); + expect(moduleConfig.getPrefix()).andReturn("/module").anyTimes(); + replay(moduleConfig); + + plugin.init(actionServlet, moduleConfig); + assertTrue(servletContext.getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX) == null); + assertTrue(servletContext.getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + "/module") != null); + + DelegatingActionProxy proxy = new DelegatingActionProxy(); + proxy.setServlet(actionServlet); + ActionMapping mapping = new ActionMapping(); + mapping.setPath("/test2"); + mapping.setModuleConfig(moduleConfig); + ActionForward forward = proxy.execute( + mapping, null, new MockHttpServletRequest(servletContext), new MockHttpServletResponse()); + assertEquals("/module/test2", forward.getPath()); + + TestAction testAction = (TestAction) plugin.getWebApplicationContext().getBean("/module/test2"); + assertTrue(testAction.getServlet() != null); + proxy.setServlet(null); + plugin.destroy(); + assertTrue(testAction.getServlet() == null); + + verify(moduleConfig); + } + + @Test + public void delegatingActionProxyWithModuleAndDefaultContext() throws Exception { + final MockServletContext servletContext = new MockServletContext("/org/springframework/web/struts/WEB-INF"); + ContextLoaderPlugIn plugin = new ContextLoaderPlugIn(); + plugin.setContextConfigLocation("action-servlet.xml"); + ActionServlet actionServlet = new ActionServlet() { + @Override + public String getServletName() { + return "action"; + } + @Override + public ServletContext getServletContext() { + return servletContext; + } + }; + + ModuleConfig defaultModuleConfig = createMock(ModuleConfig.class); + expect(defaultModuleConfig.getPrefix()).andReturn("").anyTimes(); + + ModuleConfig moduleConfig = createMock(ModuleConfig.class); + expect(moduleConfig.getPrefix()).andReturn("/module").anyTimes(); + + replay(defaultModuleConfig, moduleConfig); + + plugin.init(actionServlet, defaultModuleConfig); + assertTrue(servletContext.getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX) != null); + assertTrue(servletContext.getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX + "/module") == null); + + DelegatingActionProxy proxy = new DelegatingActionProxy(); + proxy.setServlet(actionServlet); + ActionMapping mapping = new ActionMapping(); + mapping.setPath("/test2"); + mapping.setModuleConfig(moduleConfig); + ActionForward forward = proxy.execute( + mapping, null, new MockHttpServletRequest(servletContext), new MockHttpServletResponse()); + assertEquals("/module/test2", forward.getPath()); + + TestAction testAction = (TestAction) plugin.getWebApplicationContext().getBean("/module/test2"); + assertTrue(testAction.getServlet() != null); + proxy.setServlet(null); + plugin.destroy(); + assertTrue(testAction.getServlet() == null); + + verify(defaultModuleConfig, moduleConfig); + } + +} diff --git a/org.springframework.web.struts/src/test/java/org/springframework/web/struts/TestAction.java b/org.springframework.web.struts/src/test/java/org/springframework/web/struts/TestAction.java new file mode 100644 index 00000000000..68dda908f51 --- /dev/null +++ b/org.springframework.web.struts/src/test/java/org/springframework/web/struts/TestAction.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2006 the original author or authors. + * + * Licensed 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.springframework.web.struts; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; + +import org.springframework.beans.factory.BeanNameAware; + +/** + * @author Juergen Hoeller + * @since 09.04.2004 + */ +public class TestAction extends Action implements BeanNameAware { + + private String beanName; + + public void setBeanName(String beanName) { + this.beanName = beanName; + } + + @Override + public ActionForward execute( + ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){ + + return new ActionForward(this.beanName); + } + +} diff --git a/org.springframework.web.struts/src/test/resources/log4j.xml b/org.springframework.web.struts/src/test/resources/log4j.xml new file mode 100644 index 00000000000..767b96d6206 --- /dev/null +++ b/org.springframework.web.struts/src/test/resources/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages.properties b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages.properties new file mode 100644 index 00000000000..57aa4782a7b --- /dev/null +++ b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages.properties @@ -0,0 +1,6 @@ +code1=message1 +code2=message2 + +# Example taken from the javadocs for the java.text.MessageFormat class +message.format.example1=At '{1,time}' on "{1,date}", there was "{2}" on planet {0,number,integer}. +message.format.example2=This is a test message in the message catalog with no args. diff --git a/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_GB.properties b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_GB.properties new file mode 100644 index 00000000000..623a71a8401 --- /dev/null +++ b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_GB.properties @@ -0,0 +1,2 @@ +# Example taken from the javadocs for the java.text.MessageFormat class +message.format.example1=At '{1,time}' on "{1,date}", there was "{2}" on station number {0,number,integer}. \ No newline at end of file diff --git a/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_US.properties b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_US.properties new file mode 100644 index 00000000000..2bdc0944860 --- /dev/null +++ b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/context-messages_en_US.properties @@ -0,0 +1,5 @@ +code1=message1 + +# Example taken from the javadocs for the java.text.MessageFormat class +message.format.example1=At '{1,time}' on "{1,date}", there was "{2}" on planet {0,number,integer}. +message.format.example2=This is a test message in the message catalog with no args. \ No newline at end of file diff --git a/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/tiles-test.xml b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/tiles-test.xml new file mode 100644 index 00000000000..27b48515116 --- /dev/null +++ b/org.springframework.web.struts/src/test/resources/org/springframework/web/servlet/view/tiles/tiles-test.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.springframework.web.struts/src/test/resources/org/springframework/web/struts/WEB-INF/action-servlet.xml b/org.springframework.web.struts/src/test/resources/org/springframework/web/struts/WEB-INF/action-servlet.xml new file mode 100644 index 00000000000..f934cc1f1ca --- /dev/null +++ b/org.springframework.web.struts/src/test/resources/org/springframework/web/struts/WEB-INF/action-servlet.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.springframework.web.struts/template.mf b/org.springframework.web.struts/template.mf new file mode 100644 index 00000000000..98f01a26f55 --- /dev/null +++ b/org.springframework.web.struts/template.mf @@ -0,0 +1,19 @@ +Bundle-SymbolicName: org.springframework.web.struts +Bundle-Name: Spring Web Struts +Bundle-Vendor: SpringSource +Bundle-ManifestVersion: 2 +Import-Template: + javax.servlet.*;version="[2.4.0, 3.0.0)", + org.apache.commons.beanutils.*;version="[1.7.0, 2.0.0)", + org.apache.commons.logging.*;version="[1.0.4, 2.0.0)", + org.apache.struts.*;version="[1.2.9, 2.0.0)", + org.springframework.beans.*;version="[3.0.0, 3.0.1)", + org.springframework.context.*;version="[3.0.0, 3.0.1)", + org.springframework.util.*;version="[3.0.0, 3.0.1)", + org.springframework.validation.*;version="[3.0.0, 3.0.1)", + org.springframework.web.*;version="[3.0.0, 3.0.1)" +Ignored-Existing-Headers: + Bnd-LastModified, + Import-Package, + Export-Package, + Tool diff --git a/org.springframework.web.struts/web-struts.iml b/org.springframework.web.struts/web-struts.iml new file mode 100644 index 00000000000..c7f4b4aac98 --- /dev/null +++ b/org.springframework.web.struts/web-struts.iml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +