From f70e482841662b9c1eafda4e95d749b4e929a300 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 5 May 2009 12:12:01 +0000 Subject: [PATCH] renamed "contextProperties" attribute to "contextParameters" (matching web.xml naming); "contextParameters" contains Servlet/PortletConfig parameters as well; added default "servletContext" and "servletConfig" environment beans; added default "portletContext" and "portletConfig" environment beans; added default web scope "application", wrapping a ServletContext/PortletContext; MockPortletSession supports destruction of session attributes on invalidation git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1094 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../mock/web/portlet/MockPortletContext.java | 17 +- .../mock/web/portlet/MockPortletSession.java | 31 +++- .../ServletWrappingPortletContext.java | 145 ++++++++++++++++++ ...tRefreshablePortletApplicationContext.java | 10 +- ...ConfigurablePortletApplicationContext.java | 15 +- .../PortletApplicationContextUtils.java | 48 +++++- .../portlet/context/PortletContextScope.java | 111 ++++++++++++++ .../StaticPortletApplicationContext.java | 10 +- .../mock/web/portlet/MockPortletContext.java | 2 +- .../mock/web/portlet/MockPortletSession.java | 33 +++- .../ServletWrappingPortletContext.java | 145 ++++++++++++++++++ .../PortletApplicationContextScopeTests.java | 128 ++++++++++++++++ .../ConfigurableWebApplicationContext.java | 9 +- .../web/context/ContextCleanupListener.java | 77 ++++++++++ .../web/context/ContextLoader.java | 4 +- .../web/context/ContextLoaderListener.java | 7 +- .../web/context/WebApplicationContext.java | 29 +++- ...tractRefreshableWebApplicationContext.java | 8 +- .../support/GenericWebApplicationContext.java | 27 +++- .../ServletContextAttributeFactoryBean.java | 9 +- .../support/ServletContextFactoryBean.java | 4 + .../ServletContextParameterFactoryBean.java | 5 + .../context/support/ServletContextScope.java | 111 ++++++++++++++ .../support/StaticWebApplicationContext.java | 10 +- .../support/WebApplicationContextUtils.java | 98 ++++++++++-- .../WebApplicationContextScopeTests.java | 100 ++++++++++++ 26 files changed, 1123 insertions(+), 70 deletions(-) create mode 100644 org.springframework.test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java create mode 100644 org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java create mode 100644 org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java create mode 100644 org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/context/PortletApplicationContextScopeTests.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/ContextCleanupListener.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextScope.java create mode 100644 org.springframework.web/src/test/java/org/springframework/web/context/request/WebApplicationContextScopeTests.java diff --git a/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java index f297a637de8..4881300357d 100644 --- a/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java +++ b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletContext.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Set; import javax.portlet.PortletContext; import javax.portlet.PortletRequestDispatcher; +import javax.activation.FileTypeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -152,7 +153,7 @@ public class MockPortletContext implements PortletContext { } public int getMajorVersion() { - return 1; + return 2; } public int getMinorVersion() { @@ -160,7 +161,7 @@ public class MockPortletContext implements PortletContext { } public String getMimeType(String filePath) { - return null; + return MimeTypeResolver.getMimeType(filePath); } public String getRealPath(String path) { @@ -262,4 +263,16 @@ public class MockPortletContext implements PortletContext { return Collections.enumeration(this.containerRuntimeOptions); } + + /** + * Inner factory class used to just introduce a Java Activation Framework + * dependency when actually asked to resolve a MIME type. + */ + private static class MimeTypeResolver { + + public static String getMimeType(String filePath) { + return FileTypeMap.getDefaultFileTypeMap().getContentType(filePath); + } + } + } diff --git a/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java index 558961121d1..5c9ffb34542 100644 --- a/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java +++ b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockPortletSession.java @@ -19,9 +19,14 @@ package org.springframework.mock.web.portlet; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import javax.portlet.PortletContext; import javax.portlet.PortletSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.springframework.mock.web.MockHttpSession; /** * Mock implementation of the {@link javax.portlet.PortletSession} interface. @@ -120,14 +125,34 @@ public class MockPortletSession implements PortletSession { return this.maxInactiveInterval; } + /** + * Clear all of this session's attributes. + */ + public void clearAttributes() { + doClearAttributes(this.portletAttributes); + doClearAttributes(this.applicationAttributes); + } + + protected void doClearAttributes(Map attributes) { + for (Iterator> it = attributes.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + String name = entry.getKey(); + Object value = entry.getValue(); + it.remove(); + if (value instanceof HttpSessionBindingListener) { + ((HttpSessionBindingListener) value).valueUnbound( + new HttpSessionBindingEvent(new MockHttpSession(), name, value)); + } + } + } + public void invalidate() { this.invalid = true; - this.portletAttributes.clear(); - this.applicationAttributes.clear(); + clearAttributes(); } public boolean isInvalid() { - return invalid; + return this.invalid; } public void setNew(boolean value) { diff --git a/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java new file mode 100644 index 00000000000..16b3979ca75 --- /dev/null +++ b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2009 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.mock.web.portlet; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import javax.portlet.PortletContext; +import javax.portlet.PortletRequestDispatcher; +import javax.servlet.ServletContext; + +import org.springframework.util.Assert; + +/** + * Mock implementation of the {@link javax.portlet.PortletContext} interface, + * wrapping an underlying {@link javax.servlet.ServletContext}. + * + * @author Juergen Hoeller + * @since 3.0 + * @see MockPortletContext + */ +public class ServletWrappingPortletContext implements PortletContext { + + private final ServletContext servletContext; + + + /** + * Create a new PortletContext wrapping the given ServletContext. + * @param servletContext the ServletContext to wrap + */ + public ServletWrappingPortletContext(ServletContext servletContext) { + Assert.notNull(servletContext, "ServletContext must not be null"); + this.servletContext = servletContext; + } + + /** + * Return the underlying ServletContext that this PortletContext wraps. + */ + public final ServletContext getServletContext() { + return this.servletContext; + } + + + public String getServerInfo() { + return this.servletContext.getServerInfo(); + } + + public PortletRequestDispatcher getRequestDispatcher(String path) { + return null; + } + + public PortletRequestDispatcher getNamedDispatcher(String name) { + return null; + } + + public InputStream getResourceAsStream(String path) { + return this.servletContext.getResourceAsStream(path); + } + + public int getMajorVersion() { + return 2; + } + + public int getMinorVersion() { + return 0; + } + + public String getMimeType(String file) { + return this.servletContext.getMimeType(file); + } + + public String getRealPath(String path) { + return this.servletContext.getRealPath(path); + } + + @SuppressWarnings("unchecked") + public Set getResourcePaths(String path) { + return this.servletContext.getResourcePaths(path); + } + + public URL getResource(String path) throws MalformedURLException { + return this.servletContext.getResource(path); + } + + public Object getAttribute(String name) { + return this.servletContext.getAttribute(name); + } + + @SuppressWarnings("unchecked") + public Enumeration getAttributeNames() { + return this.servletContext.getAttributeNames(); + } + + public String getInitParameter(String name) { + return this.servletContext.getInitParameter(name); + } + + @SuppressWarnings("unchecked") + public Enumeration getInitParameterNames() { + return this.servletContext.getInitParameterNames(); + } + + public void log(String msg) { + this.servletContext.log(msg); + } + + public void log(String message, Throwable throwable) { + this.servletContext.log(message, throwable); + } + + public void removeAttribute(String name) { + this.servletContext.removeAttribute(name); + } + + public void setAttribute(String name, Object object) { + this.servletContext.setAttribute(name, object); + } + + public String getPortletContextName() { + return this.servletContext.getServletContextName(); + } + + public Enumeration getContainerRuntimeOptions() { + return Collections.enumeration(new HashSet()); + } + +} diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java index b10750fa932..db136444e57 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -143,12 +143,10 @@ public abstract class AbstractRefreshablePortletApplicationContext extends Abstr beanFactory.ignoreDependencyInterface(ServletContextAware.class); beanFactory.ignoreDependencyInterface(PortletContextAware.class); beanFactory.ignoreDependencyInterface(PortletConfigAware.class); - beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext); - beanFactory.registerResolvableDependency(PortletContext.class, this.portletContext); - beanFactory.registerResolvableDependency(PortletConfig.class, this.portletConfig); - PortletApplicationContextUtils.registerPortletApplicationScopes(beanFactory); - PortletApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.portletContext); + PortletApplicationContextUtils.registerPortletApplicationScopes(beanFactory, this.portletContext); + PortletApplicationContextUtils.registerEnvironmentBeans( + beanFactory, this.servletContext, this.portletContext, this.portletConfig); } /** diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/ConfigurablePortletApplicationContext.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/ConfigurablePortletApplicationContext.java index 857d5718172..45452354f1d 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/ConfigurablePortletApplicationContext.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/ConfigurablePortletApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -43,6 +43,19 @@ import org.springframework.web.context.WebApplicationContext; public interface ConfigurablePortletApplicationContext extends WebApplicationContext, ConfigurableApplicationContext { + /** + * Name of the PortletContext environment bean in the factory. + * @see javax.portlet.PortletContext + */ + String PORTLET_CONTEXT_BEAN_NAME = "portletContext"; + + /** + * Name of the PortletConfig environment bean in the factory. + * @see javax.portlet.PortletConfig + */ + String PORTLET_CONFIG_BEAN_NAME = "portletConfig"; + + /** * Set the PortletContext for this portlet application context. *

Does not cause an initialization of the context: refresh needs to be diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java index e4c52863294..71b65c5186a 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletApplicationContextUtils.java @@ -20,9 +20,11 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import javax.portlet.PortletConfig; import javax.portlet.PortletContext; import javax.portlet.PortletRequest; import javax.portlet.PortletSession; +import javax.servlet.ServletContext; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -102,14 +104,21 @@ public abstract class PortletApplicationContextUtils { /** - * Register portlet-specific scopes with the given BeanFactory, - * as used by the Portlet ApplicationContext. + * Register web-specific scopes ("request", "session", "globalSession") + * with the given BeanFactory, as used by the Portlet ApplicationContext. * @param beanFactory the BeanFactory to configure + * @param pc the PortletContext that we're running within */ - static void registerPortletApplicationScopes(ConfigurableListableBeanFactory beanFactory) { + static void registerPortletApplicationScopes(ConfigurableListableBeanFactory beanFactory, PortletContext pc) { beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false)); beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true)); + if (pc != null) { + PortletContextScope appScope = new PortletContextScope(pc); + beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); + // Register as PortletContext attribute, for ContextCleanupListener to detect it. + pc.setAttribute(PortletContextScope.class.getName(), appScope); + } beanFactory.registerResolvableDependency(PortletRequest.class, new ObjectFactory() { public PortletRequest getObject() { @@ -132,13 +141,29 @@ public abstract class PortletApplicationContextUtils { } /** - * Register web-specific environment beans with the given BeanFactory, - * as used by the Portlet ApplicationContext. + * Register web-specific environment beans ("contextParameters", "contextAttributes") + * with the given BeanFactory, as used by the Portlet ApplicationContext. * @param bf the BeanFactory to configure + * @param sc the ServletContext that we're running within * @param pc the PortletContext that we're running within + * @param config the PortletConfig of the containing Portlet */ - static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, PortletContext pc) { - if (!bf.containsBean(WebApplicationContext.CONTEXT_PROPERTIES_BEAN_NAME)) { + static void registerEnvironmentBeans( + ConfigurableListableBeanFactory bf, ServletContext sc, PortletContext pc, PortletConfig config) { + + if (sc != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) { + bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, sc); + } + + if (pc != null && !bf.containsBean(ConfigurablePortletApplicationContext.PORTLET_CONTEXT_BEAN_NAME)) { + bf.registerSingleton(ConfigurablePortletApplicationContext.PORTLET_CONTEXT_BEAN_NAME, pc); + } + + if (config != null && !bf.containsBean(ConfigurablePortletApplicationContext.PORTLET_CONFIG_BEAN_NAME)) { + bf.registerSingleton(ConfigurablePortletApplicationContext.PORTLET_CONFIG_BEAN_NAME, config); + } + + if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) { Map parameterMap = new HashMap(); if (pc != null) { Enumeration paramNameEnum = pc.getInitParameterNames(); @@ -147,7 +172,14 @@ public abstract class PortletApplicationContextUtils { parameterMap.put(paramName, pc.getInitParameter(paramName)); } } - bf.registerSingleton(WebApplicationContext.CONTEXT_PROPERTIES_BEAN_NAME, + if (config != null) { + Enumeration paramNameEnum = config.getInitParameterNames(); + while (paramNameEnum.hasMoreElements()) { + String paramName = (String) paramNameEnum.nextElement(); + parameterMap.put(paramName, config.getInitParameter(paramName)); + } + } + bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME, Collections.unmodifiableMap(parameterMap)); } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java new file mode 100644 index 00000000000..459ae6a77b9 --- /dev/null +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletContextScope.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2009 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.portlet.context; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.portlet.PortletContext; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.util.Assert; + +/** + * {@link Scope} wrapper for a PortletContext, i.e. for global web application attributes. + * + *

This differs from traditional Spring singletons in that it exposes attributes in the + * PortletContext. Those attributes will get destroyed whenever the entire application + * shuts down, which might be earlier or later than the shutdown of the containing Spring + * ApplicationContext. + * + *

The associated destruction mechanism relies on a + * {@link org.springframework.web.context.ContextCleanupListener} being registered in + * web.xml. Note that {@link org.springframework.web.context.ContextLoaderListener} + * includes ContextCleanupListener's functionality. + * + *

This scope is registered as default scope with key + * {@link org.springframework.web.context.WebApplicationContext#SCOPE_APPLICATION "application"}. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.web.context.ContextCleanupListener + */ +public class PortletContextScope implements Scope, DisposableBean { + + private final PortletContext portletContext; + + private final Map destructionCallbacks = new LinkedHashMap(); + + + /** + * Create a new Scope wrapper for the given PortletContext. + * @param PortletContext the PortletContext to wrap + */ + public PortletContextScope(PortletContext portletContext) { + Assert.notNull(portletContext, "PortletContext must not be null"); + this.portletContext = portletContext; + } + + + public Object get(String name, ObjectFactory objectFactory) { + Object scopedObject = this.portletContext.getAttribute(name); + if (scopedObject == null) { + scopedObject = objectFactory.getObject(); + this.portletContext.setAttribute(name, scopedObject); + } + return scopedObject; + } + + public Object remove(String name) { + Object scopedObject = this.portletContext.getAttribute(name); + if (scopedObject != null) { + this.portletContext.removeAttribute(name); + this.destructionCallbacks.remove(name); + return scopedObject; + } + else { + return null; + } + } + + public void registerDestructionCallback(String name, Runnable callback) { + this.destructionCallbacks.put(name, callback); + } + + public Object resolveContextualObject(String key) { + return null; + } + + public String getConversationId() { + return null; + } + + + /** + * Invoke all registered destruction callbacks. + * To be called on ServletContext shutdown. + * @see org.springframework.web.context.ContextCleanupListener + */ + public void destroy() { + for (Runnable runnable : this.destructionCallbacks.values()) { + runnable.run(); + } + this.destructionCallbacks.clear(); + } + +} diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java index 457b4c2cad0..ecaf20061f1 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/StaticPortletApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -139,12 +139,10 @@ public class StaticPortletApplicationContext extends StaticApplicationContext beanFactory.addBeanPostProcessor(new PortletContextAwareProcessor(this.portletContext, this.portletConfig)); beanFactory.ignoreDependencyInterface(PortletContextAware.class); beanFactory.ignoreDependencyInterface(PortletConfigAware.class); - beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext); - beanFactory.registerResolvableDependency(PortletContext.class, this.portletContext); - beanFactory.registerResolvableDependency(PortletConfig.class, this.portletConfig); - PortletApplicationContextUtils.registerPortletApplicationScopes(beanFactory); - PortletApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.portletContext); + PortletApplicationContextUtils.registerPortletApplicationScopes(beanFactory, this.portletContext); + PortletApplicationContextUtils.registerEnvironmentBeans( + beanFactory, this.servletContext, this.portletContext, this.portletConfig); } /** diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletContext.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletContext.java index f297a637de8..0cf9f24ffe8 100644 --- a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletContext.java +++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletContext.java @@ -152,7 +152,7 @@ public class MockPortletContext implements PortletContext { } public int getMajorVersion() { - return 1; + return 2; } public int getMinorVersion() { diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletSession.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletSession.java index 558961121d1..d9632906f76 100644 --- a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletSession.java +++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletSession.java @@ -19,9 +19,14 @@ package org.springframework.mock.web.portlet; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import javax.portlet.PortletContext; import javax.portlet.PortletSession; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import org.springframework.mock.web.MockHttpSession; /** * Mock implementation of the {@link javax.portlet.PortletSession} interface. @@ -70,7 +75,7 @@ public class MockPortletSession implements PortletSession { this.portletContext = (portletContext != null ? portletContext : new MockPortletContext()); } - + public Object getAttribute(String name) { return this.portletAttributes.get(name); } @@ -120,14 +125,34 @@ public class MockPortletSession implements PortletSession { return this.maxInactiveInterval; } + /** + * Clear all of this session's attributes. + */ + public void clearAttributes() { + doClearAttributes(this.portletAttributes); + doClearAttributes(this.applicationAttributes); + } + + protected void doClearAttributes(Map attributes) { + for (Iterator> it = attributes.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + String name = entry.getKey(); + Object value = entry.getValue(); + it.remove(); + if (value instanceof HttpSessionBindingListener) { + ((HttpSessionBindingListener) value).valueUnbound( + new HttpSessionBindingEvent(new MockHttpSession(), name, value)); + } + } + } + public void invalidate() { this.invalid = true; - this.portletAttributes.clear(); - this.applicationAttributes.clear(); + clearAttributes(); } public boolean isInvalid() { - return invalid; + return this.invalid; } public void setNew(boolean value) { diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java new file mode 100644 index 00000000000..d796d3ea453 --- /dev/null +++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/ServletWrappingPortletContext.java @@ -0,0 +1,145 @@ +/* + * Copyright 2002-2009 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.mock.web.portlet; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import javax.portlet.PortletContext; +import javax.portlet.PortletRequestDispatcher; +import javax.servlet.ServletContext; + +import org.springframework.util.Assert; + +/** + * Mock implementation of the {@link javax.portlet.PortletContext} interface, + * wrapping an underlying {@link javax.servlet.ServletContext}. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.mock.web.portlet.MockPortletContext + */ +public class ServletWrappingPortletContext implements PortletContext { + + private final ServletContext servletContext; + + + /** + * Create a new PortletContext wrapping the given ServletContext. + * @param servletContext the ServletContext to wrap + */ + public ServletWrappingPortletContext(ServletContext servletContext) { + Assert.notNull(servletContext, "ServletContext must not be null"); + this.servletContext = servletContext; + } + + /** + * Return the underlying ServletContext that this PortletContext wraps. + */ + public final ServletContext getServletContext() { + return this.servletContext; + } + + + public String getServerInfo() { + return this.servletContext.getServerInfo(); + } + + public PortletRequestDispatcher getRequestDispatcher(String path) { + return null; + } + + public PortletRequestDispatcher getNamedDispatcher(String name) { + return null; + } + + public InputStream getResourceAsStream(String path) { + return this.servletContext.getResourceAsStream(path); + } + + public int getMajorVersion() { + return 2; + } + + public int getMinorVersion() { + return 0; + } + + public String getMimeType(String file) { + return this.servletContext.getMimeType(file); + } + + public String getRealPath(String path) { + return this.servletContext.getRealPath(path); + } + + @SuppressWarnings("unchecked") + public Set getResourcePaths(String path) { + return this.servletContext.getResourcePaths(path); + } + + public URL getResource(String path) throws MalformedURLException { + return this.servletContext.getResource(path); + } + + public Object getAttribute(String name) { + return this.servletContext.getAttribute(name); + } + + @SuppressWarnings("unchecked") + public Enumeration getAttributeNames() { + return this.servletContext.getAttributeNames(); + } + + public String getInitParameter(String name) { + return this.servletContext.getInitParameter(name); + } + + @SuppressWarnings("unchecked") + public Enumeration getInitParameterNames() { + return this.servletContext.getInitParameterNames(); + } + + public void log(String msg) { + this.servletContext.log(msg); + } + + public void log(String message, Throwable throwable) { + this.servletContext.log(message, throwable); + } + + public void removeAttribute(String name) { + this.servletContext.removeAttribute(name); + } + + public void setAttribute(String name, Object object) { + this.servletContext.setAttribute(name, object); + } + + public String getPortletContextName() { + return this.servletContext.getServletContextName(); + } + + public Enumeration getContainerRuntimeOptions() { + return Collections.enumeration(new HashSet()); + } + +} diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/context/PortletApplicationContextScopeTests.java b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/context/PortletApplicationContextScopeTests.java new file mode 100644 index 00000000000..64201e768a1 --- /dev/null +++ b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/context/PortletApplicationContextScopeTests.java @@ -0,0 +1,128 @@ +/* + * Copyright 2002-2009 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.portlet.context; + +import javax.portlet.PortletContext; +import javax.portlet.PortletSession; +import javax.servlet.ServletContextEvent; + +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.beans.DerivedTestBean; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.mock.web.MockServletContext; +import org.springframework.mock.web.portlet.MockRenderRequest; +import org.springframework.mock.web.portlet.ServletWrappingPortletContext; +import org.springframework.web.context.ContextCleanupListener; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.support.GenericWebApplicationContext; + +/** + * @author Juergen Hoeller + */ +public class PortletApplicationContextScopeTests { + + private static final String NAME = "scoped"; + + + private ConfigurablePortletApplicationContext initApplicationContext(String scope) { + MockServletContext sc = new MockServletContext(); + GenericWebApplicationContext rac = new GenericWebApplicationContext(sc); + rac.refresh(); + PortletContext pc = new ServletWrappingPortletContext(sc); + StaticPortletApplicationContext ac = new StaticPortletApplicationContext(); + ac.setParent(rac); + ac.setPortletContext(pc); + GenericBeanDefinition bd = new GenericBeanDefinition(); + bd.setBeanClass(DerivedTestBean.class); + bd.setScope(scope); + ac.registerBeanDefinition(NAME, bd); + ac.refresh(); + return ac; + } + + @Test + public void testRequestScope() { + WebApplicationContext ac = initApplicationContext(WebApplicationContext.SCOPE_REQUEST); + MockRenderRequest request = new MockRenderRequest(); + PortletRequestAttributes requestAttributes = new PortletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(requestAttributes); + try { + assertNull(request.getAttribute(NAME)); + DerivedTestBean bean = ac.getBean(NAME, DerivedTestBean.class); + assertSame(bean, request.getAttribute(NAME)); + assertSame(bean, ac.getBean(NAME)); + requestAttributes.requestCompleted(); + assertTrue(bean.wasDestroyed()); + } + finally { + RequestContextHolder.setRequestAttributes(null); + } + } + + @Test + public void testSessionScope() { + WebApplicationContext ac = initApplicationContext(WebApplicationContext.SCOPE_SESSION); + MockRenderRequest request = new MockRenderRequest(); + PortletRequestAttributes requestAttributes = new PortletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(requestAttributes); + try { + assertNull(request.getPortletSession().getAttribute(NAME)); + DerivedTestBean bean = ac.getBean(NAME, DerivedTestBean.class); + assertSame(bean, request.getPortletSession().getAttribute(NAME)); + assertSame(bean, ac.getBean(NAME)); + request.getPortletSession().invalidate(); + assertTrue(bean.wasDestroyed()); + } + finally { + RequestContextHolder.setRequestAttributes(null); + } + } + + @Test + public void testGlobalSessionScope() { + WebApplicationContext ac = initApplicationContext(WebApplicationContext.SCOPE_GLOBAL_SESSION); + MockRenderRequest request = new MockRenderRequest(); + PortletRequestAttributes requestAttributes = new PortletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(requestAttributes); + try { + assertNull(request.getPortletSession().getAttribute(NAME, PortletSession.APPLICATION_SCOPE)); + DerivedTestBean bean = ac.getBean(NAME, DerivedTestBean.class); + assertSame(bean, request.getPortletSession().getAttribute(NAME, PortletSession.APPLICATION_SCOPE)); + assertSame(bean, ac.getBean(NAME)); + request.getPortletSession().invalidate(); + assertTrue(bean.wasDestroyed()); + } + finally { + RequestContextHolder.setRequestAttributes(null); + } + } + + @Test + public void testApplicationScope() { + ConfigurablePortletApplicationContext ac = initApplicationContext(WebApplicationContext.SCOPE_APPLICATION); + assertNull(ac.getPortletContext().getAttribute(NAME)); + DerivedTestBean bean = ac.getBean(NAME, DerivedTestBean.class); + assertSame(bean, ac.getPortletContext().getAttribute(NAME)); + assertSame(bean, ac.getBean(NAME)); + new ContextCleanupListener().contextDestroyed(new ServletContextEvent(ac.getServletContext())); + assertTrue(bean.wasDestroyed()); + } + +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java index 119703c035f..9c8ffa4192a 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -39,6 +39,13 @@ import org.springframework.context.ConfigurableApplicationContext; */ public interface ConfigurableWebApplicationContext extends WebApplicationContext, ConfigurableApplicationContext { + /** + * Name of the ServletConfig environment bean in the factory. + * @see javax.servlet.ServletConfig + */ + String SERVLET_CONFIG_BEAN_NAME = "servletConfig"; + + /** * Set the ServletContext for this web application context. *

Does not cause an initialization of the context: refresh needs to be diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextCleanupListener.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextCleanupListener.java new file mode 100644 index 00000000000..71db226277a --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextCleanupListener.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2009 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.context; + +import java.util.Enumeration; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.DisposableBean; + +/** + * Web application listener that cleans up remaining disposable attributes + * in the ServletContext, i.e. attributes which implement {@link DisposableBean} + * and haven't been removed before. This is typically used for destroying objects + * in "application" scope, for which the lifecycle implies destruction at the + * very end of the web application's shutdown phase. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.web.context.support.ServletContextScope + * @see ContextLoaderListener + */ +public class ContextCleanupListener implements ServletContextListener { + + private static final Log logger = LogFactory.getLog(ContextCleanupListener.class); + + + public void contextInitialized(ServletContextEvent event) { + } + + public void contextDestroyed(ServletContextEvent event) { + cleanupAttributes(event.getServletContext()); + } + + + /** + * Find all ServletContext attributes which implement {@link DisposableBean} + * and destroy them, removing all affected ServletContext attributes eventually. + * @param sc the ServletContext to check + */ + static void cleanupAttributes(ServletContext sc) { + Enumeration attrNames = sc.getAttributeNames(); + while (attrNames.hasMoreElements()) { + String attrName = (String) attrNames.nextElement(); + if (attrName.startsWith("org.springframework.")) { + Object attrValue = sc.getAttribute(attrName); + if (attrValue instanceof DisposableBean) { + try { + ((DisposableBean) attrValue).destroy(); + } + catch (Throwable ex) { + logger.error("Couldn't invoke destroy method of attribute with name '" + attrName + "'", ex); + } + } + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java index 6281d964114..d926fdcef02 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -269,7 +269,7 @@ public class ContextLoader { String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); if (contextClassName != null) { try { - return ClassUtils.forName(contextClassName); + return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } catch (ClassNotFoundException ex) { throw new ApplicationContextException( diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java index a0657e15de4..b0140f08a85 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/ContextLoaderListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -20,8 +20,8 @@ import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; /** - * Bootstrap listener to start up Spring's root {@link WebApplicationContext}. - * Simply delegates to {@link ContextLoader}. + * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}. + * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}. * *

This listener should be registered after * {@link org.springframework.web.util.Log4jConfigListener} @@ -77,6 +77,7 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte if (this.contextLoader != null) { this.contextLoader.closeWebApplicationContext(event.getServletContext()); } + ContextCleanupListener.cleanupAttributes(event.getServletContext()); } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java index d5ede5d6a8e..48ae350aeab 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/WebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -72,14 +72,30 @@ public interface WebApplicationContext extends ApplicationContext { String SCOPE_GLOBAL_SESSION = "globalSession"; /** - * Name of the ServletContext init-params environment bean in the factory. - * @see javax.servlet.ServletContext#getInitParameterNames() - * @see javax.servlet.ServletContext#getInitParameter(String) + * Scope identifier for the global web application scope: "application". + * Supported in addition to the standard scopes "singleton" and "prototype". */ - String CONTEXT_PROPERTIES_BEAN_NAME = "contextProperties"; + String SCOPE_APPLICATION = "application"; /** - * Name of the ServletContext attributes environment bean in the factory. + * Name of the ServletContext environment bean in the factory. + * @see javax.servlet.ServletContext + */ + String SERVLET_CONTEXT_BEAN_NAME = "servletContext"; + + /** + * Name of the ServletContext/PortletContext init-params environment bean in the factory. + *

Note: Possibly merged with ServletConfig/PortletConfig parameters. + * ServletConfig parameters override ServletContext parameters of the same name. + * @see javax.servlet.ServletContext#getInitParameterNames() + * @see javax.servlet.ServletContext#getInitParameter(String) + * @see javax.servlet.ServletConfig#getInitParameterNames() + * @see javax.servlet.ServletConfig#getInitParameter(String) + */ + String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters"; + + /** + * Name of the ServletContext/PortletContext attributes environment bean in the factory. * @see javax.servlet.ServletContext#getAttributeNames() * @see javax.servlet.ServletContext#getAttribute(String) */ @@ -88,6 +104,7 @@ public interface WebApplicationContext extends ApplicationContext { /** * Return the standard Servlet API ServletContext for this application. + *

Also available for a Portlet application, in addition to the PortletContext. */ ServletContext getServletContext(); diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java index 70fa076c5d2..58d2a081825 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -139,11 +139,9 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); beanFactory.ignoreDependencyInterface(ServletConfigAware.class); - beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext); - beanFactory.registerResolvableDependency(ServletConfig.class, this.servletConfig); - WebApplicationContextUtils.registerWebApplicationScopes(beanFactory); - WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext); + WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext); + WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig); } /** diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java index e59b406f48b..69293d20803 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -71,6 +71,16 @@ public class GenericWebApplicationContext extends GenericApplicationContext super(); } + /** + * Create a new GenericWebApplicationContext for the given ServletContext. + * @param servletContext the ServletContext to run in + * @see #registerBeanDefinition + * @see #refresh + */ + public GenericWebApplicationContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + /** * Create a new GenericWebApplicationContext with the given DefaultListableBeanFactory. * @param beanFactory the DefaultListableBeanFactory instance to use for this context @@ -82,6 +92,18 @@ public class GenericWebApplicationContext extends GenericApplicationContext super(beanFactory); } + /** + * Create a new GenericWebApplicationContext with the given DefaultListableBeanFactory. + * @param beanFactory the DefaultListableBeanFactory instance to use for this context + * @param servletContext the ServletContext to run in + * @see #registerBeanDefinition + * @see #refresh + */ + public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, ServletContext servletContext) { + super(beanFactory); + this.servletContext = servletContext; + } + /** * Set the ServletContext that this WebApplicationContext runs in. @@ -103,9 +125,8 @@ public class GenericWebApplicationContext extends GenericApplicationContext protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); - beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext); - WebApplicationContextUtils.registerWebApplicationScopes(beanFactory); + WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext); WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java index 4d35f31c6e4..da3ca2419c8 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextAttributeFactoryBean.java @@ -30,10 +30,15 @@ import org.springframework.web.context.ServletContextAware; * the startup of the Spring application context. Typically, such * attributes will have been put there by third-party web frameworks. * In a purely Spring-based web application, no such linking in of - * ServletContext attrutes will be necessary. + * ServletContext attributes will be necessary. + * + *

NOTE: As of Spring 3.0, you may also use the "contextAttributes" default + * bean which is of type Map, and dereference it using an "#{contextAttributes.myKey}" + * expression to access a specific attribute by name. * * @author Juergen Hoeller * @since 1.1.4 + * @see org.springframework.web.context.WebApplicationContext#CONTEXT_ATTRIBUTES_BEAN_NAME * @see ServletContextParameterFactoryBean */ public class ServletContextAttributeFactoryBean implements FactoryBean, ServletContextAware { @@ -52,7 +57,7 @@ public class ServletContextAttributeFactoryBean implements FactoryBean, public void setServletContext(ServletContext servletContext) { if (this.attributeName == null) { - throw new IllegalArgumentException("attributeName is required"); + throw new IllegalArgumentException("Property 'attributeName' is required"); } this.attribute = servletContext.getAttribute(this.attributeName); if (this.attribute == null) { diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java index 1dba16785c0..f5af5566588 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextFactoryBean.java @@ -39,7 +39,11 @@ import org.springframework.web.context.ServletContextAware; * @see javax.servlet.ServletContext * @see org.springframework.web.context.ServletContextAware * @see ServletContextAttributeFactoryBean + * @see org.springframework.web.context.WebApplicationContext#SERVLET_CONTEXT_BEAN_NAME + * @deprecated as of Spring 3.0, since "servletContext" is now available + * as a default bean in every WebApplicationContext */ +@Deprecated public class ServletContextFactoryBean implements FactoryBean, ServletContextAware { private ServletContext servletContext; diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java index b5dd06647df..5107229b469 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextParameterFactoryBean.java @@ -27,8 +27,13 @@ import org.springframework.web.context.ServletContextAware; * Exposes that ServletContext init parameter when used as bean reference, * effectively making it available as named Spring bean instance. * + *

NOTE: As of Spring 3.0, you may also use the "contextParameters" default + * bean which is of type Map, and dereference it using an "#{contextParameters.myKey}" + * expression to access a specific parameter by name. + * * @author Juergen Hoeller * @since 1.2.4 + * @see org.springframework.web.context.WebApplicationContext#CONTEXT_PARAMETERS_BEAN_NAME * @see ServletContextAttributeFactoryBean */ public class ServletContextParameterFactoryBean implements FactoryBean, ServletContextAware { diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextScope.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextScope.java new file mode 100644 index 00000000000..a4cc9fc814c --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/ServletContextScope.java @@ -0,0 +1,111 @@ +/* + * Copyright 2002-2009 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.context.support; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.servlet.ServletContext; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.util.Assert; + +/** + * {@link Scope} wrapper for a ServletContext, i.e. for global web application attributes. + * + *

This differs from traditional Spring singletons in that it exposes attributes in the + * ServletContext. Those attributes will get destroyed whenever the entire application + * shuts down, which might be earlier or later than the shutdown of the containing Spring + * ApplicationContext. + * + *

The associated destruction mechanism relies on a + * {@link org.springframework.web.context.ContextCleanupListener} being registered in + * web.xml. Note that {@link org.springframework.web.context.ContextLoaderListener} + * includes ContextCleanupListener's functionality. + * + *

This scope is registered as default scope with key + * {@link org.springframework.web.context.WebApplicationContext#SCOPE_APPLICATION "application"}. + * + * @author Juergen Hoeller + * @since 3.0 + * @see org.springframework.web.context.ContextCleanupListener + */ +public class ServletContextScope implements Scope, DisposableBean { + + private final ServletContext servletContext; + + private final Map destructionCallbacks = new LinkedHashMap(); + + + /** + * Create a new Scope wrapper for the given ServletContext. + * @param servletContext the ServletContext to wrap + */ + public ServletContextScope(ServletContext servletContext) { + Assert.notNull(servletContext, "ServletContext must not be null"); + this.servletContext = servletContext; + } + + + public Object get(String name, ObjectFactory objectFactory) { + Object scopedObject = this.servletContext.getAttribute(name); + if (scopedObject == null) { + scopedObject = objectFactory.getObject(); + this.servletContext.setAttribute(name, scopedObject); + } + return scopedObject; + } + + public Object remove(String name) { + Object scopedObject = this.servletContext.getAttribute(name); + if (scopedObject != null) { + this.servletContext.removeAttribute(name); + this.destructionCallbacks.remove(name); + return scopedObject; + } + else { + return null; + } + } + + public void registerDestructionCallback(String name, Runnable callback) { + this.destructionCallbacks.put(name, callback); + } + + public Object resolveContextualObject(String key) { + return null; + } + + public String getConversationId() { + return null; + } + + + /** + * Invoke all registered destruction callbacks. + * To be called on ServletContext shutdown. + * @see org.springframework.web.context.ContextCleanupListener + */ + public void destroy() { + for (Runnable runnable : this.destructionCallbacks.values()) { + runnable.run(); + } + this.destructionCallbacks.clear(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java index 7a638e08e66..740fb880203 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2009 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. @@ -29,8 +29,6 @@ import org.springframework.ui.context.support.UiApplicationContextUtils; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ServletConfigAware; import org.springframework.web.context.ServletContextAware; -import org.springframework.web.context.request.RequestScope; -import org.springframework.web.context.request.SessionScope; /** * Static {@link org.springframework.web.context.WebApplicationContext} @@ -137,11 +135,9 @@ public class StaticWebApplicationContext extends StaticApplicationContext beanFactory.addBeanPostProcessor(new ServletContextAwareProcessor(this.servletContext, this.servletConfig)); beanFactory.ignoreDependencyInterface(ServletContextAware.class); beanFactory.ignoreDependencyInterface(ServletConfigAware.class); - beanFactory.registerResolvableDependency(ServletContext.class, this.servletContext); - beanFactory.registerResolvableDependency(ServletConfig.class, this.servletConfig); - WebApplicationContextUtils.registerWebApplicationScopes(beanFactory); - WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext); + WebApplicationContextUtils.registerWebApplicationScopes(beanFactory, this.servletContext); + WebApplicationContextUtils.registerEnvironmentBeans(beanFactory, this.servletContext, this.servletConfig); } /** diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java index 15a828148de..771d50766f3 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/support/WebApplicationContextUtils.java @@ -20,6 +20,9 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import javax.servlet.http.HttpSession; @@ -27,6 +30,8 @@ import javax.servlet.http.HttpSession; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -49,10 +54,15 @@ import org.springframework.web.context.request.SessionScope; * @see org.springframework.web.servlet.FrameworkServlet * @see org.springframework.web.servlet.DispatcherServlet * @see org.springframework.web.jsf.FacesContextUtils - * @see org.springframework.web.jsf.DelegatingVariableResolver + * @see org.springframework.web.jsf.SpringBeanVariableResolver + * @see org.springframework.web.jsf.el.SpringBeanFacesELResolver */ public abstract class WebApplicationContextUtils { - + + private static final boolean jsfPresent = + ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader()); + + /** * Find the root WebApplicationContext for this web application, which is * typically loaded via {@link org.springframework.web.context.ContextLoaderListener}. @@ -64,7 +74,7 @@ public abstract class WebApplicationContextUtils { * @see org.springframework.web.context.WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE */ public static WebApplicationContext getRequiredWebApplicationContext(ServletContext sc) - throws IllegalStateException { + throws IllegalStateException { WebApplicationContext wac = getWebApplicationContext(sc); if (wac == null) { @@ -115,14 +125,30 @@ public abstract class WebApplicationContextUtils { /** - * Register web-specific scopes with the given BeanFactory, - * as used by the WebApplicationContext. + * Register web-specific scopes ("request", "session", "globalSession") + * with the given BeanFactory, as used by the WebApplicationContext. * @param beanFactory the BeanFactory to configure */ public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory) { + registerWebApplicationScopes(beanFactory, null); + } + + /** + * Register web-specific scopes ("request", "session", "globalSession", "application") + * with the given BeanFactory, as used by the WebApplicationContext. + * @param beanFactory the BeanFactory to configure + * @param sc the ServletContext that we're running within + */ + public static void registerWebApplicationScopes(ConfigurableListableBeanFactory beanFactory, ServletContext sc) { beanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope()); beanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope(false)); beanFactory.registerScope(WebApplicationContext.SCOPE_GLOBAL_SESSION, new SessionScope(true)); + if (sc != null) { + ServletContextScope appScope = new ServletContextScope(sc); + beanFactory.registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); + // Register as ServletContext attribute, for ContextCleanupListener to detect it. + sc.setAttribute(ServletContextScope.class.getName(), appScope); + } beanFactory.registerResolvableDependency(ServletRequest.class, new ObjectFactory() { public ServletRequest getObject() { @@ -142,16 +168,41 @@ public abstract class WebApplicationContextUtils { return ((ServletRequestAttributes) requestAttr).getRequest().getSession(); } }); + + if (jsfPresent) { + FacesDependencyRegistrar.registerFacesDependencies(beanFactory); + } } /** - * Register web-specific environment beans with the given BeanFactory, - * as used by the WebApplicationContext. + * Register web-specific environment beans ("contextParameters", "contextAttributes") + * with the given BeanFactory, as used by the WebApplicationContext. * @param bf the BeanFactory to configure * @param sc the ServletContext that we're running within */ - static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, ServletContext sc) { - if (!bf.containsBean(WebApplicationContext.CONTEXT_PROPERTIES_BEAN_NAME)) { + public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, ServletContext sc) { + registerEnvironmentBeans(bf, sc, null); + } + + /** + * Register web-specific environment beans ("contextParameters", "contextAttributes") + * with the given BeanFactory, as used by the WebApplicationContext. + * @param bf the BeanFactory to configure + * @param sc the ServletContext that we're running within + * @param config the ServletConfig of the containing Portlet + */ + public static void registerEnvironmentBeans( + ConfigurableListableBeanFactory bf, ServletContext sc, ServletConfig config) { + + if (sc != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) { + bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, sc); + } + + if (config != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) { + bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, config); + } + + if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) { Map parameterMap = new HashMap(); if (sc != null) { Enumeration paramNameEnum = sc.getInitParameterNames(); @@ -160,7 +211,14 @@ public abstract class WebApplicationContextUtils { parameterMap.put(paramName, sc.getInitParameter(paramName)); } } - bf.registerSingleton(WebApplicationContext.CONTEXT_PROPERTIES_BEAN_NAME, + if (config != null) { + Enumeration paramNameEnum = config.getInitParameterNames(); + while (paramNameEnum.hasMoreElements()) { + String paramName = (String) paramNameEnum.nextElement(); + parameterMap.put(paramName, config.getInitParameter(paramName)); + } + } + bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME, Collections.unmodifiableMap(parameterMap)); } @@ -178,4 +236,24 @@ public abstract class WebApplicationContextUtils { } } + + /** + * Inner class to avoid hard-coded JSF dependency. + */ + private static class FacesDependencyRegistrar { + + public static void registerFacesDependencies(ConfigurableListableBeanFactory beanFactory) { + beanFactory.registerResolvableDependency(FacesContext.class, new ObjectFactory() { + public FacesContext getObject() { + return FacesContext.getCurrentInstance(); + } + }); + beanFactory.registerResolvableDependency(ExternalContext.class, new ObjectFactory() { + public ExternalContext getObject() { + return FacesContext.getCurrentInstance().getExternalContext(); + } + }); + } + } + } diff --git a/org.springframework.web/src/test/java/org/springframework/web/context/request/WebApplicationContextScopeTests.java b/org.springframework.web/src/test/java/org/springframework/web/context/request/WebApplicationContextScopeTests.java new file mode 100644 index 00000000000..26ef5cd60e6 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/web/context/request/WebApplicationContextScopeTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2002-2009 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.context.request; + +import javax.servlet.ServletContextEvent; + +import static org.junit.Assert.*; +import org.junit.Test; + +import org.springframework.beans.DerivedTestBean; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.ContextCleanupListener; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.GenericWebApplicationContext; + +/** + * @author Juergen Hoeller + */ +public class WebApplicationContextScopeTests { + + private static final String NAME = "scoped"; + + + private WebApplicationContext initApplicationContext(String scope) { + MockServletContext sc = new MockServletContext(); + GenericWebApplicationContext ac = new GenericWebApplicationContext(sc); + GenericBeanDefinition bd = new GenericBeanDefinition(); + bd.setBeanClass(DerivedTestBean.class); + bd.setScope(scope); + ac.registerBeanDefinition(NAME, bd); + ac.refresh(); + return ac; + } + + @Test + public void testRequestScope() { + WebApplicationContext ac = initApplicationContext(WebApplicationContext.SCOPE_REQUEST); + MockHttpServletRequest request = new MockHttpServletRequest(); + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(requestAttributes); + try { + assertNull(request.getAttribute(NAME)); + DerivedTestBean bean = ac.getBean(NAME, DerivedTestBean.class); + assertSame(bean, request.getAttribute(NAME)); + assertSame(bean, ac.getBean(NAME)); + requestAttributes.requestCompleted(); + assertTrue(bean.wasDestroyed()); + } + finally { + RequestContextHolder.setRequestAttributes(null); + } + } + + @Test + public void testSessionScope() { + WebApplicationContext ac = initApplicationContext(WebApplicationContext.SCOPE_SESSION); + MockHttpServletRequest request = new MockHttpServletRequest(); + ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); + RequestContextHolder.setRequestAttributes(requestAttributes); + try { + assertNull(request.getSession().getAttribute(NAME)); + DerivedTestBean bean = ac.getBean(NAME, DerivedTestBean.class); + assertSame(bean, request.getSession().getAttribute(NAME)); + assertSame(bean, ac.getBean(NAME)); + request.getSession().invalidate(); + assertTrue(bean.wasDestroyed()); + } + finally { + RequestContextHolder.setRequestAttributes(null); + } + } + + @Test + public void testApplicationScope() { + WebApplicationContext ac = initApplicationContext(WebApplicationContext.SCOPE_APPLICATION); + assertNull(ac.getServletContext().getAttribute(NAME)); + DerivedTestBean bean = ac.getBean(NAME, DerivedTestBean.class); + assertSame(bean, ac.getServletContext().getAttribute(NAME)); + assertSame(bean, ac.getBean(NAME)); + new ContextCleanupListener().contextDestroyed(new ServletContextEvent(ac.getServletContext())); + assertTrue(bean.wasDestroyed()); + } + +}