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

This commit is contained in:
Juergen Hoeller
2009-05-05 12:12:01 +00:00
parent 0a0938fdd6
commit b15e77d5d0
26 changed files with 1123 additions and 70 deletions

View File

@@ -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);
}
/**

View File

@@ -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.
* <p>Does not cause an initialization of the context: refresh needs to be

View File

@@ -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<PortletRequest>() {
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<String, String> parameterMap = new HashMap<String, String>();
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));
}

View File

@@ -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.
*
* <p>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.
*
* <p>The associated destruction mechanism relies on a
* {@link org.springframework.web.context.ContextCleanupListener} being registered in
* <code>web.xml</code>. Note that {@link org.springframework.web.context.ContextLoaderListener}
* includes ContextCleanupListener's functionality.
*
* <p>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<String, Runnable> destructionCallbacks = new LinkedHashMap<String, Runnable>();
/**
* 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();
}
}

View File

@@ -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);
}
/**

View File

@@ -152,7 +152,7 @@ public class MockPortletContext implements PortletContext {
}
public int getMajorVersion() {
return 1;
return 2;
}
public int getMinorVersion() {

View File

@@ -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<String, Object> attributes) {
for (Iterator<Map.Entry<String, Object>> it = attributes.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Object> 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) {

View File

@@ -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<String> 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<String> getAttributeNames() {
return this.servletContext.getAttributeNames();
}
public String getInitParameter(String name) {
return this.servletContext.getInitParameter(name);
}
@SuppressWarnings("unchecked")
public Enumeration<String> 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<String> getContainerRuntimeOptions() {
return Collections.enumeration(new HashSet<String>());
}
}

View File

@@ -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());
}
}