View Javadoc
1   /*
2    * Copyright 2002-2014 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.mock.web.test;
18  
19  import java.io.Serializable;
20  import java.util.Collections;
21  import java.util.Enumeration;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.LinkedHashMap;
25  import java.util.LinkedHashSet;
26  import java.util.Map;
27  import javax.servlet.ServletContext;
28  import javax.servlet.http.HttpSession;
29  import javax.servlet.http.HttpSessionBindingEvent;
30  import javax.servlet.http.HttpSessionBindingListener;
31  import javax.servlet.http.HttpSessionContext;
32  
33  import org.springframework.util.Assert;
34  
35  /**
36   * Mock implementation of the {@link javax.servlet.http.HttpSession} interface.
37   *
38   * <p>As of Spring 4.0, this set of mocks is designed on a Servlet 3.0 baseline.
39   *
40   * <p>Used for testing the web framework; also useful for testing application
41   * controllers.
42   *
43   * @author Juergen Hoeller
44   * @author Rod Johnson
45   * @author Mark Fisher
46   * @author Sam Brannen
47   * @since 1.0.2
48   */
49  @SuppressWarnings("deprecation")
50  public class MockHttpSession implements HttpSession {
51  
52  	public static final String SESSION_COOKIE_NAME = "JSESSION";
53  
54  
55  	private static int nextId = 1;
56  
57  	private String id;
58  
59  	private final long creationTime = System.currentTimeMillis();
60  
61  	private int maxInactiveInterval;
62  
63  	private long lastAccessedTime = System.currentTimeMillis();
64  
65  	private final ServletContext servletContext;
66  
67  	private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
68  
69  	private boolean invalid = false;
70  
71  	private boolean isNew = true;
72  
73  
74  	/**
75  	 * Create a new MockHttpSession with a default {@link MockServletContext}.
76  	 *
77  	 * @see MockServletContext
78  	 */
79  	public MockHttpSession() {
80  		this(null);
81  	}
82  
83  	/**
84  	 * Create a new MockHttpSession.
85  	 *
86  	 * @param servletContext the ServletContext that the session runs in
87  	 */
88  	public MockHttpSession(ServletContext servletContext) {
89  		this(servletContext, null);
90  	}
91  
92  	/**
93  	 * Create a new MockHttpSession.
94  	 *
95  	 * @param servletContext the ServletContext that the session runs in
96  	 * @param id a unique identifier for this session
97  	 */
98  	public MockHttpSession(ServletContext servletContext, String id) {
99  		this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
100 		this.id = (id != null ? id : Integer.toString(nextId++));
101 	}
102 
103 	@Override
104 	public long getCreationTime() {
105 		assertIsValid();
106 		return this.creationTime;
107 	}
108 
109 	@Override
110 	public String getId() {
111 		return this.id;
112 	}
113 
114 	/**
115 	 * As of Servlet 3.1 the id of a session can be changed.
116 	 * @return the new session id.
117 	 * @since 4.0.3
118 	 */
119 	public String changeSessionId() {
120 		this.id = Integer.toString(nextId++);
121 		return this.id;
122 	}
123 
124 	public void access() {
125 		this.lastAccessedTime = System.currentTimeMillis();
126 		this.isNew = false;
127 	}
128 
129 	@Override
130 	public long getLastAccessedTime() {
131 		assertIsValid();
132 		return this.lastAccessedTime;
133 	}
134 
135 	@Override
136 	public ServletContext getServletContext() {
137 		return this.servletContext;
138 	}
139 
140 	@Override
141 	public void setMaxInactiveInterval(int interval) {
142 		this.maxInactiveInterval = interval;
143 	}
144 
145 	@Override
146 	public int getMaxInactiveInterval() {
147 		return this.maxInactiveInterval;
148 	}
149 
150 	@Override
151 	public HttpSessionContext getSessionContext() {
152 		throw new UnsupportedOperationException("getSessionContext");
153 	}
154 
155 	@Override
156 	public Object getAttribute(String name) {
157 		assertIsValid();
158 		Assert.notNull(name, "Attribute name must not be null");
159 		return this.attributes.get(name);
160 	}
161 
162 	@Override
163 	public Object getValue(String name) {
164 		return getAttribute(name);
165 	}
166 
167 	@Override
168 	public Enumeration<String> getAttributeNames() {
169 		assertIsValid();
170 		return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet()));
171 	}
172 
173 	@Override
174 	public String[] getValueNames() {
175 		assertIsValid();
176 		return this.attributes.keySet().toArray(new String[this.attributes.size()]);
177 	}
178 
179 	@Override
180 	public void setAttribute(String name, Object value) {
181 		assertIsValid();
182 		Assert.notNull(name, "Attribute name must not be null");
183 		if (value != null) {
184 			this.attributes.put(name, value);
185 			if (value instanceof HttpSessionBindingListener) {
186 				((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
187 			}
188 		}
189 		else {
190 			removeAttribute(name);
191 		}
192 	}
193 
194 	@Override
195 	public void putValue(String name, Object value) {
196 		setAttribute(name, value);
197 	}
198 
199 	@Override
200 	public void removeAttribute(String name) {
201 		assertIsValid();
202 		Assert.notNull(name, "Attribute name must not be null");
203 		Object value = this.attributes.remove(name);
204 		if (value instanceof HttpSessionBindingListener) {
205 			((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
206 		}
207 	}
208 
209 	@Override
210 	public void removeValue(String name) {
211 		removeAttribute(name);
212 	}
213 
214 	/**
215 	 * Clear all of this session's attributes.
216 	 */
217 	public void clearAttributes() {
218 		for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) {
219 			Map.Entry<String, Object> entry = it.next();
220 			String name = entry.getKey();
221 			Object value = entry.getValue();
222 			it.remove();
223 			if (value instanceof HttpSessionBindingListener) {
224 				((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
225 			}
226 		}
227 	}
228 
229 	/**
230 	 * Invalidates this session then unbinds any objects bound to it.
231 	 *
232 	 * @throws IllegalStateException if this method is called on an already invalidated session
233 	 */
234 	@Override
235 	public void invalidate() {
236 		assertIsValid();
237 		this.invalid = true;
238 		clearAttributes();
239 	}
240 
241 	public boolean isInvalid() {
242 		return this.invalid;
243 	}
244 
245 	/**
246 	 * Convenience method for asserting that this session has not been
247 	 * {@linkplain #invalidate() invalidated}.
248 	 *
249 	 * @throws IllegalStateException if this session has been invalidated
250 	 */
251 	private void assertIsValid() {
252 		if (isInvalid()) {
253 			throw new IllegalStateException("The session has already been invalidated");
254 		}
255 	}
256 
257 	public void setNew(boolean value) {
258 		this.isNew = value;
259 	}
260 
261 	@Override
262 	public boolean isNew() {
263 		assertIsValid();
264 		return this.isNew;
265 	}
266 
267 	/**
268 	 * Serialize the attributes of this session into an object that can be
269 	 * turned into a byte array with standard Java serialization.
270 	 *
271 	 * @return a representation of this session's serialized state
272 	 */
273 	public Serializable serializeState() {
274 		HashMap<String, Serializable> state = new HashMap<String, Serializable>();
275 		for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) {
276 			Map.Entry<String, Object> entry = it.next();
277 			String name = entry.getKey();
278 			Object value = entry.getValue();
279 			it.remove();
280 			if (value instanceof Serializable) {
281 				state.put(name, (Serializable) value);
282 			}
283 			else {
284 				// Not serializable... Servlet containers usually automatically
285 				// unbind the attribute in this case.
286 				if (value instanceof HttpSessionBindingListener) {
287 					((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
288 				}
289 			}
290 		}
291 		return state;
292 	}
293 
294 	/**
295 	 * Deserialize the attributes of this session from a state object created by
296 	 * {@link #serializeState()}.
297 	 *
298 	 * @param state a representation of this session's serialized state
299 	 */
300 	@SuppressWarnings("unchecked")
301 	public void deserializeState(Serializable state) {
302 		Assert.isTrue(state instanceof Map, "Serialized state needs to be of type [java.util.Map]");
303 		this.attributes.putAll((Map<String, Object>) state);
304 	}
305 
306 }