diff --git a/build-spring-framework/resources/changelog.txt b/build-spring-framework/resources/changelog.txt index ae57f45e774..b680d5faa62 100644 --- a/build-spring-framework/resources/changelog.txt +++ b/build-spring-framework/resources/changelog.txt @@ -34,6 +34,7 @@ Changes in version 3.1.1 (2012-02-06) * add normalize() method to UriComponents * remove empty path segments from input to UriComponentsBuilder * add fromRequestUri(request) and fromCurrentRequestUri() methods to ServletUriCommonentsBuilder +* improve @SessionAttributes handling to provide better support for clustered sessions Changes in version 3.1 GA (2011-12-12) -------------------------------------- diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java index fd7b4e42cf6..5e6080db13e 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/SessionAttributesHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -31,16 +31,16 @@ import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.context.request.WebRequest; /** - * Manages controller-specific session attributes declared via - * {@link SessionAttributes @SessionAttributes}. Actual storage is - * performed via {@link SessionAttributeStore}. - * - *

When a controller annotated with {@code @SessionAttributes} adds - * attributes to its model, those attributes are checked against names and - * types specified via {@code @SessionAttributes}. Matching model attributes - * are saved in the HTTP session and remain there until the controller calls + * Manages controller-specific session attributes declared via + * {@link SessionAttributes @SessionAttributes}. Actual storage is + * delegated to a {@link SessionAttributeStore} instance. + * + *

When a controller annotated with {@code @SessionAttributes} adds + * attributes to its model, those attributes are checked against names and + * types specified via {@code @SessionAttributes}. Matching model attributes + * are saved in the HTTP session and remain there until the controller calls * {@link SessionStatus#setComplete()}. - * + * * @author Rossen Stoyanchev * @since 3.1 */ @@ -50,51 +50,53 @@ public class SessionAttributesHandler { private final Set> attributeTypes = new HashSet>(); - private final Set resolvedAttributeNames = Collections.synchronizedSet(new HashSet(4)); + private final Set knownAttributeNames = Collections.synchronizedSet(new HashSet(4)); private final SessionAttributeStore sessionAttributeStore; /** - * Creates a new instance for a controller type. Session attribute names/types - * are extracted from a type-level {@code @SessionAttributes} if found. + * Create a new instance for a controller type. Session attribute names and + * types are extracted from the {@code @SessionAttributes} annotation, if + * present, on the given type. * @param handlerType the controller type * @param sessionAttributeStore used for session access */ public SessionAttributesHandler(Class handlerType, SessionAttributeStore sessionAttributeStore) { Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null."); this.sessionAttributeStore = sessionAttributeStore; - + SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class); if (annotation != null) { - this.attributeNames.addAll(Arrays.asList(annotation.value())); + this.attributeNames.addAll(Arrays.asList(annotation.value())); this.attributeTypes.addAll(Arrays.>asList(annotation.types())); - } + } + + this.knownAttributeNames.addAll(this.attributeNames); } /** - * Whether the controller represented by this instance has declared session - * attribute names or types of interest via {@link SessionAttributes}. + * Whether the controller represented by this instance has declared any + * session attributes through an {@link SessionAttributes} annotation. */ public boolean hasSessionAttributes() { - return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0)); + return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0)); } - + /** - * Whether the attribute name and/or type match those specified in the - * controller's {@code @SessionAttributes} annotation. - * + * Whether the attribute name or type match the names and types specified + * via {@code @SessionAttributes} in underlying controller. + * *

Attributes successfully resolved through this method are "remembered" - * and used in {@link #retrieveAttributes(WebRequest)} and - * {@link #cleanupAttributes(WebRequest)}. In other words, retrieval and - * cleanup only affect attributes previously resolved through here. - * - * @param attributeName the attribute name to check; must not be null - * @param attributeType the type for the attribute; or {@code null} + * and subsequently used in {@link #retrieveAttributes(WebRequest)} and + * {@link #cleanupAttributes(WebRequest)}. + * + * @param attributeName the attribute name to check, never {@code null} + * @param attributeType the type for the attribute, possibly {@code null} */ public boolean isHandlerSessionAttribute(String attributeName, Class attributeType) { Assert.notNull(attributeName, "Attribute name must not be null"); if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) { - this.resolvedAttributeNames.add(attributeName); + this.knownAttributeNames.add(attributeName); return true; } else { @@ -103,8 +105,8 @@ public class SessionAttributesHandler { } /** - * Stores a subset of the given attributes in the session. Attributes not - * declared as session attributes via {@code @SessionAttributes} are ignored. + * Store a subset of the given attributes in the session. Attributes not + * declared as session attributes via {@code @SessionAttributes} are ignored. * @param request the current request * @param attributes candidate attributes for session storage */ @@ -112,23 +114,23 @@ public class SessionAttributesHandler { for (String name : attributes.keySet()) { Object value = attributes.get(name); Class attrType = (value != null) ? value.getClass() : null; - + if (isHandlerSessionAttribute(name, attrType)) { this.sessionAttributeStore.storeAttribute(request, name, value); } } } - + /** - * Retrieve "known" attributes from the session -- i.e. attributes listed - * in {@code @SessionAttributes} and previously stored in the in the model - * at least once. + * Retrieve "known" attributes from the session, i.e. attributes listed + * by name in {@code @SessionAttributes} or attributes previously stored + * in the model that matched by type. * @param request the current request - * @return a map with handler session attributes; possibly empty. + * @return a map with handler session attributes, possibly empty */ public Map retrieveAttributes(WebRequest request) { Map attributes = new HashMap(); - for (String name : this.resolvedAttributeNames) { + for (String name : this.knownAttributeNames) { Object value = this.sessionAttributeStore.retrieveAttribute(request, name); if (value != null) { attributes.put(name, value); @@ -138,13 +140,13 @@ public class SessionAttributesHandler { } /** - * Cleans "known" attributes from the session - i.e. attributes listed - * in {@code @SessionAttributes} and previously stored in the in the model - * at least once. + * Remove "known" attributes from the session, i.e. attributes listed + * by name in {@code @SessionAttributes} or attributes previously stored + * in the model that matched by type. * @param request the current request */ public void cleanupAttributes(WebRequest request) { - for (String attributeName : this.resolvedAttributeNames) { + for (String attributeName : this.knownAttributeNames) { this.sessionAttributeStore.cleanupAttribute(request, attributeName); } } @@ -158,5 +160,5 @@ public class SessionAttributesHandler { Object retrieveAttribute(WebRequest request, String attributeName) { return this.sessionAttributeStore.retrieveAttribute(request, attributeName); } - + } \ No newline at end of file diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java index 06575004b95..8007e5143bc 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/SessionAttributesHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -24,7 +24,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.HashSet; -import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -39,17 +38,17 @@ import org.springframework.web.context.request.ServletWebRequest; /** * Test fixture with {@link SessionAttributesHandler}. - * + * * @author Rossen Stoyanchev */ public class SessionAttributesHandlerTests { private Class handlerType = SessionAttributeHandler.class; - + private SessionAttributesHandler sessionAttributesHandler; - + private SessionAttributeStore sessionAttributeStore; - + private NativeWebRequest request; @Before @@ -58,7 +57,7 @@ public class SessionAttributesHandlerTests { this.sessionAttributesHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore); this.request = new ServletWebRequest(new MockHttpServletRequest()); } - + @Test public void isSessionAttribute() throws Exception { assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); @@ -72,14 +71,18 @@ public class SessionAttributesHandlerTests { sessionAttributeStore.storeAttribute(request, "attr1", "value1"); sessionAttributeStore.storeAttribute(request, "attr2", "value2"); sessionAttributeStore.storeAttribute(request, "attr3", new TestBean()); + sessionAttributeStore.storeAttribute(request, "attr4", new TestBean()); - // Resolve successfully handler session attributes once - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class)); + assertEquals("Named attributes (attr1, attr2) should be 'known' right away", + new HashSet(asList("attr1", "attr2")), + sessionAttributesHandler.retrieveAttributes(request).keySet()); - Map attributes = sessionAttributesHandler.retrieveAttributes(request); + // Resolve 'attr3' by type + sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class); - assertEquals(new HashSet(asList("attr1", "attr3")), attributes.keySet()); + assertEquals("Named attributes (attr1, attr2) and resolved attribute (att3) should be 'known'", + new HashSet(asList("attr1", "attr2", "attr3")), + sessionAttributesHandler.retrieveAttributes(request).keySet()); } @Test @@ -88,14 +91,16 @@ public class SessionAttributesHandlerTests { sessionAttributeStore.storeAttribute(request, "attr2", "value2"); sessionAttributeStore.storeAttribute(request, "attr3", new TestBean()); - // Resolve successfully handler session attributes once - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class)); - sessionAttributesHandler.cleanupAttributes(request); - + assertNull(sessionAttributeStore.retrieveAttribute(request, "attr1")); - assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr2")); + assertNull(sessionAttributeStore.retrieveAttribute(request, "attr2")); + assertNotNull(sessionAttributeStore.retrieveAttribute(request, "attr3")); + + // Resolve 'attr3' by type + sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class); + sessionAttributesHandler.cleanupAttributes(request); + assertNull(sessionAttributeStore.retrieveAttribute(request, "attr3")); } @@ -105,19 +110,14 @@ public class SessionAttributesHandlerTests { model.put("attr1", "value1"); model.put("attr2", "value2"); model.put("attr3", new TestBean()); - - // Resolve successfully handler session attributes once - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr1", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr2", null)); - assertTrue(sessionAttributesHandler.isHandlerSessionAttribute("attr3", TestBean.class)); - + sessionAttributesHandler.storeAttributes(request, model); - + assertEquals("value1", sessionAttributeStore.retrieveAttribute(request, "attr1")); assertEquals("value2", sessionAttributeStore.retrieveAttribute(request, "attr2")); assertTrue(sessionAttributeStore.retrieveAttribute(request, "attr3") instanceof TestBean); } - + @SessionAttributes(value = { "attr1", "attr2" }, types = { TestBean.class }) private static class SessionAttributeHandler { }