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}