001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.mock.web; 018 019import java.io.Serializable; 020import java.util.Collections; 021import java.util.Enumeration; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.LinkedHashMap; 025import java.util.LinkedHashSet; 026import java.util.Map; 027import javax.servlet.ServletContext; 028import javax.servlet.http.HttpSession; 029import javax.servlet.http.HttpSessionBindingEvent; 030import javax.servlet.http.HttpSessionBindingListener; 031 032import org.springframework.util.Assert; 033import org.springframework.util.StringUtils; 034 035/** 036 * Mock implementation of the {@link javax.servlet.http.HttpSession} interface. 037 * 038 * <p>As of Spring 4.0, this set of mocks is designed on a Servlet 3.0 baseline. 039 * 040 * <p>Used for testing the web framework; also useful for testing application 041 * controllers. 042 * 043 * @author Juergen Hoeller 044 * @author Rod Johnson 045 * @author Mark Fisher 046 * @author Sam Brannen 047 * @since 1.0.2 048 */ 049@SuppressWarnings("deprecation") 050public class MockHttpSession implements HttpSession { 051 052 public static final String SESSION_COOKIE_NAME = "JSESSION"; 053 054 055 private static int nextId = 1; 056 057 private String id; 058 059 private final long creationTime = System.currentTimeMillis(); 060 061 private int maxInactiveInterval; 062 063 private long lastAccessedTime = System.currentTimeMillis(); 064 065 private final ServletContext servletContext; 066 067 private final Map<String, Object> attributes = new LinkedHashMap<String, Object>(); 068 069 private boolean invalid = false; 070 071 private boolean isNew = true; 072 073 074 /** 075 * Create a new MockHttpSession with a default {@link MockServletContext}. 076 * @see MockServletContext 077 */ 078 public MockHttpSession() { 079 this(null); 080 } 081 082 /** 083 * Create a new MockHttpSession. 084 * @param servletContext the ServletContext that the session runs in 085 */ 086 public MockHttpSession(ServletContext servletContext) { 087 this(servletContext, null); 088 } 089 090 /** 091 * Create a new MockHttpSession. 092 * @param servletContext the ServletContext that the session runs in 093 * @param id a unique identifier for this session 094 */ 095 public MockHttpSession(ServletContext servletContext, String id) { 096 this.servletContext = (servletContext != null ? servletContext : new MockServletContext()); 097 this.id = (id != null ? id : Integer.toString(nextId++)); 098 } 099 100 101 @Override 102 public long getCreationTime() { 103 assertIsValid(); 104 return this.creationTime; 105 } 106 107 @Override 108 public String getId() { 109 return this.id; 110 } 111 112 /** 113 * As of Servlet 3.1, the id of a session can be changed. 114 * @return the new session id 115 * @since 4.0.3 116 */ 117 public String changeSessionId() { 118 this.id = Integer.toString(nextId++); 119 return this.id; 120 } 121 122 public void access() { 123 this.lastAccessedTime = System.currentTimeMillis(); 124 this.isNew = false; 125 } 126 127 @Override 128 public long getLastAccessedTime() { 129 assertIsValid(); 130 return this.lastAccessedTime; 131 } 132 133 @Override 134 public ServletContext getServletContext() { 135 return this.servletContext; 136 } 137 138 @Override 139 public void setMaxInactiveInterval(int interval) { 140 this.maxInactiveInterval = interval; 141 } 142 143 @Override 144 public int getMaxInactiveInterval() { 145 return this.maxInactiveInterval; 146 } 147 148 @Override 149 public javax.servlet.http.HttpSessionContext getSessionContext() { 150 throw new UnsupportedOperationException("getSessionContext"); 151 } 152 153 @Override 154 public Object getAttribute(String name) { 155 assertIsValid(); 156 Assert.notNull(name, "Attribute name must not be null"); 157 return this.attributes.get(name); 158 } 159 160 @Override 161 public Object getValue(String name) { 162 return getAttribute(name); 163 } 164 165 @Override 166 public Enumeration<String> getAttributeNames() { 167 assertIsValid(); 168 return Collections.enumeration(new LinkedHashSet<String>(this.attributes.keySet())); 169 } 170 171 @Override 172 public String[] getValueNames() { 173 assertIsValid(); 174 return StringUtils.toStringArray(this.attributes.keySet()); 175 } 176 177 @Override 178 public void setAttribute(String name, Object value) { 179 assertIsValid(); 180 Assert.notNull(name, "Attribute name must not be null"); 181 if (value != null) { 182 this.attributes.put(name, value); 183 if (value instanceof HttpSessionBindingListener) { 184 ((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value)); 185 } 186 } 187 else { 188 removeAttribute(name); 189 } 190 } 191 192 @Override 193 public void putValue(String name, Object value) { 194 setAttribute(name, value); 195 } 196 197 @Override 198 public void removeAttribute(String name) { 199 assertIsValid(); 200 Assert.notNull(name, "Attribute name must not be null"); 201 Object value = this.attributes.remove(name); 202 if (value instanceof HttpSessionBindingListener) { 203 ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); 204 } 205 } 206 207 @Override 208 public void removeValue(String name) { 209 removeAttribute(name); 210 } 211 212 /** 213 * Clear all of this session's attributes. 214 */ 215 public void clearAttributes() { 216 for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) { 217 Map.Entry<String, Object> entry = it.next(); 218 String name = entry.getKey(); 219 Object value = entry.getValue(); 220 it.remove(); 221 if (value instanceof HttpSessionBindingListener) { 222 ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); 223 } 224 } 225 } 226 227 /** 228 * Invalidates this session then unbinds any objects bound to it. 229 * @throws IllegalStateException if this method is called on an already invalidated session 230 */ 231 @Override 232 public void invalidate() { 233 assertIsValid(); 234 this.invalid = true; 235 clearAttributes(); 236 } 237 238 public boolean isInvalid() { 239 return this.invalid; 240 } 241 242 /** 243 * Convenience method for asserting that this session has not been 244 * {@linkplain #invalidate() invalidated}. 245 * @throws IllegalStateException if this session has been invalidated 246 */ 247 private void assertIsValid() { 248 Assert.state(!isInvalid(), "The session has already been invalidated"); 249 } 250 251 public void setNew(boolean value) { 252 this.isNew = value; 253 } 254 255 @Override 256 public boolean isNew() { 257 assertIsValid(); 258 return this.isNew; 259 } 260 261 /** 262 * Serialize the attributes of this session into an object that can be 263 * turned into a byte array with standard Java serialization. 264 * @return a representation of this session's serialized state 265 */ 266 public Serializable serializeState() { 267 HashMap<String, Serializable> state = new HashMap<String, Serializable>(); 268 for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) { 269 Map.Entry<String, Object> entry = it.next(); 270 String name = entry.getKey(); 271 Object value = entry.getValue(); 272 it.remove(); 273 if (value instanceof Serializable) { 274 state.put(name, (Serializable) value); 275 } 276 else { 277 // Not serializable... Servlet containers usually automatically 278 // unbind the attribute in this case. 279 if (value instanceof HttpSessionBindingListener) { 280 ((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value)); 281 } 282 } 283 } 284 return state; 285 } 286 287 /** 288 * Deserialize the attributes of this session from a state object created by 289 * {@link #serializeState()}. 290 * @param state a representation of this session's serialized state 291 */ 292 @SuppressWarnings("unchecked") 293 public void deserializeState(Serializable state) { 294 Assert.isTrue(state instanceof Map, "Serialized state needs to be of type [java.util.Map]"); 295 this.attributes.putAll((Map<String, Object>) state); 296 } 297 298}