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}