View Javadoc
1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.method.annotation;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.concurrent.ConcurrentHashMap;
26  
27  import org.springframework.core.annotation.AnnotationUtils;
28  import org.springframework.util.Assert;
29  import org.springframework.web.bind.annotation.SessionAttributes;
30  import org.springframework.web.bind.support.SessionAttributeStore;
31  import org.springframework.web.bind.support.SessionStatus;
32  import org.springframework.web.context.request.WebRequest;
33  
34  /**
35   * Manages controller-specific session attributes declared via
36   * {@link SessionAttributes @SessionAttributes}. Actual storage is
37   * delegated to a {@link SessionAttributeStore} instance.
38   *
39   * <p>When a controller annotated with {@code @SessionAttributes} adds
40   * attributes to its model, those attributes are checked against names and
41   * types specified via {@code @SessionAttributes}. Matching model attributes
42   * are saved in the HTTP session and remain there until the controller calls
43   * {@link SessionStatus#setComplete()}.
44   *
45   * @author Rossen Stoyanchev
46   * @since 3.1
47   */
48  public class SessionAttributesHandler {
49  
50  	private final Set<String> attributeNames = new HashSet<String>();
51  
52  	private final Set<Class<?>> attributeTypes = new HashSet<Class<?>>();
53  
54  	private final Set<String> knownAttributeNames =
55  			Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(4));
56  
57  	private final SessionAttributeStore sessionAttributeStore;
58  
59  
60  	/**
61  	 * Create a new instance for a controller type. Session attribute names and
62  	 * types are extracted from the {@code @SessionAttributes} annotation, if
63  	 * present, on the given type.
64  	 * @param handlerType the controller type
65  	 * @param sessionAttributeStore used for session access
66  	 */
67  	public SessionAttributesHandler(Class<?> handlerType, SessionAttributeStore sessionAttributeStore) {
68  		Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null.");
69  		this.sessionAttributeStore = sessionAttributeStore;
70  
71  		SessionAttributes annotation = AnnotationUtils.findAnnotation(handlerType, SessionAttributes.class);
72  		if (annotation != null) {
73  			this.attributeNames.addAll(Arrays.asList(annotation.value()));
74  			this.attributeTypes.addAll(Arrays.<Class<?>>asList(annotation.types()));
75  		}
76  
77  		for (String attributeName : this.attributeNames) {
78  			this.knownAttributeNames.add(attributeName);
79  		}
80  	}
81  
82  	/**
83  	 * Whether the controller represented by this instance has declared any
84  	 * session attributes through an {@link SessionAttributes} annotation.
85  	 */
86  	public boolean hasSessionAttributes() {
87  		return ((this.attributeNames.size() > 0) || (this.attributeTypes.size() > 0));
88  	}
89  
90  	/**
91  	 * Whether the attribute name or type match the names and types specified
92  	 * via {@code @SessionAttributes} in underlying controller.
93  	 *
94  	 * <p>Attributes successfully resolved through this method are "remembered"
95  	 * and subsequently used in {@link #retrieveAttributes(WebRequest)} and
96  	 * {@link #cleanupAttributes(WebRequest)}.
97  	 *
98  	 * @param attributeName the attribute name to check, never {@code null}
99  	 * @param attributeType the type for the attribute, possibly {@code null}
100 	 */
101 	public boolean isHandlerSessionAttribute(String attributeName, Class<?> attributeType) {
102 		Assert.notNull(attributeName, "Attribute name must not be null");
103 		if (this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType)) {
104 			this.knownAttributeNames.add(attributeName);
105 			return true;
106 		}
107 		else {
108 			return false;
109 		}
110 	}
111 
112 	/**
113 	 * Store a subset of the given attributes in the session. Attributes not
114 	 * declared as session attributes via {@code @SessionAttributes} are ignored.
115 	 * @param request the current request
116 	 * @param attributes candidate attributes for session storage
117 	 */
118 	public void storeAttributes(WebRequest request, Map<String, ?> attributes) {
119 		for (String name : attributes.keySet()) {
120 			Object value = attributes.get(name);
121 			Class<?> attrType = (value != null) ? value.getClass() : null;
122 
123 			if (isHandlerSessionAttribute(name, attrType)) {
124 				this.sessionAttributeStore.storeAttribute(request, name, value);
125 			}
126 		}
127 	}
128 
129 	/**
130 	 * Retrieve "known" attributes from the session, i.e. attributes listed
131 	 * by name in {@code @SessionAttributes} or attributes previously stored
132 	 * in the model that matched by type.
133 	 * @param request the current request
134 	 * @return a map with handler session attributes, possibly empty
135 	 */
136 	public Map<String, Object> retrieveAttributes(WebRequest request) {
137 		Map<String, Object> attributes = new HashMap<String, Object>();
138 		for (String name : this.knownAttributeNames) {
139 			Object value = this.sessionAttributeStore.retrieveAttribute(request, name);
140 			if (value != null) {
141 				attributes.put(name, value);
142 			}
143 		}
144 		return attributes;
145 	}
146 
147 	/**
148 	 * Remove "known" attributes from the session, i.e. attributes listed
149 	 * by name in {@code @SessionAttributes} or attributes previously stored
150 	 * in the model that matched by type.
151 	 * @param request the current request
152 	 */
153 	public void cleanupAttributes(WebRequest request) {
154 		for (String attributeName : this.knownAttributeNames) {
155 			this.sessionAttributeStore.cleanupAttribute(request, attributeName);
156 		}
157 	}
158 
159 	/**
160 	 * A pass-through call to the underlying {@link SessionAttributeStore}.
161 	 * @param request the current request
162 	 * @param attributeName the name of the attribute of interest
163 	 * @return the attribute value or {@code null}
164 	 */
165 	Object retrieveAttribute(WebRequest request, String attributeName) {
166 		return this.sessionAttributeStore.retrieveAttribute(request, attributeName);
167 	}
168 
169 }