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