001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2010–2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2014 ForgeRock AS.
016 */
017
018package org.forgerock.openig.servlet;
019
020import java.io.IOException;
021import java.util.AbstractMap;
022import java.util.AbstractSet;
023import java.util.ArrayList;
024import java.util.Enumeration;
025import java.util.Iterator;
026import java.util.List;
027import java.util.NoSuchElementException;
028import java.util.Set;
029
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.http.HttpSession;
032
033import org.forgerock.openig.http.Session;
034
035/**
036 * Exposes the session managed by the servlet container as an exchange session.
037 * This implementation will get a servlet session if already allocated,
038 * otherwise will not create one until an attempt is made to put an attribute in
039 * it.
040 */
041public class ServletSession extends AbstractMap<String, Object> implements Session {
042
043    /** The servlet request from which to get a servlet session object. */
044    private final HttpServletRequest request;
045
046    /** The servlet session object, if available. */
047    private volatile HttpSession httpSession;
048
049    /** The Map entrySet view of the session attributes. */
050    private final Set<Entry<String, Object>> attributes = new AbstractSet<Entry<String, Object>>() {
051        @Override
052        public void clear() {
053            ServletSession.this.clear();
054        }
055
056        @Override
057        public boolean contains(final Object o) {
058            return (o instanceof Entry)
059                    && ServletSession.this.containsKey(((Entry<?, ?>) o).getKey());
060        }
061
062        @Override
063        public boolean isEmpty() {
064            return ServletSession.this.isEmpty();
065        }
066
067        @Override
068        public Iterator<Entry<String, Object>> iterator() {
069            return new Iterator<Entry<String, Object>>() {
070                @SuppressWarnings("unchecked")
071                final Enumeration<String> names = httpSession != null ? httpSession
072                        .getAttributeNames() : null;
073
074                @Override
075                public boolean hasNext() {
076                    return names != null && names.hasMoreElements();
077                }
078
079                @Override
080                public Entry<String, Object> next() {
081                    if (names == null) {
082                        throw new NoSuchElementException();
083                    }
084                    final String name = names.nextElement();
085                    return new SimpleEntry<String, Object>(name, httpSession.getAttribute(name)) {
086                        private static final long serialVersionUID = -2957899005221454275L;
087
088                        @Override
089                        public Object setValue(final Object value) {
090                            put(getKey(), value);
091                            return super.setValue(value);
092                        }
093                    };
094                }
095
096                @Override
097                public void remove() {
098                    // Enumerations do not support concurrent removals.
099                    throw new UnsupportedOperationException();
100                }
101            };
102        }
103
104        @Override
105        public boolean remove(final Object o) {
106            return (o instanceof Entry)
107                    && ServletSession.this.remove(((Entry<?, ?>) o).getKey()) != null;
108        }
109
110        @Override
111        public int size() {
112            return ServletSession.this.size();
113        }
114    };
115
116    /**
117     * Creates a new session object which manages sessions through the provided
118     * servlet request object.
119     *
120     * @param request the servlet request object through which servlet sessions are
121     * managed.
122     */
123    public ServletSession(final HttpServletRequest request) {
124        this.request = request;
125        // get session if already allocated
126        this.httpSession = request.getSession(false);
127    }
128
129    @Override
130    public void clear() {
131        if (httpSession != null) {
132            // Do in 2 steps to avoid CME.
133            @SuppressWarnings("unchecked")
134            final Enumeration<String> attributes = httpSession.getAttributeNames();
135            final List<String> names = new ArrayList<String>();
136            while (attributes.hasMoreElements()) {
137                names.add(attributes.nextElement());
138            }
139            for (final String name : names) {
140                httpSession.removeAttribute(name);
141            }
142        }
143    }
144
145    @Override
146    public boolean containsKey(final Object key) {
147        return get(key) != null;
148    }
149
150    @Override
151    public Set<Entry<String, Object>> entrySet() {
152        return attributes;
153    }
154
155    @Override
156    public Object get(final Object key) {
157        Object value = null;
158        if (key instanceof String && httpSession != null) {
159            value = httpSession.getAttribute((String) key);
160        }
161        return value;
162    }
163
164    @Override
165    public boolean isEmpty() {
166        return httpSession == null || !httpSession.getAttributeNames().hasMoreElements();
167    }
168
169    @Override
170    public synchronized Object put(final String key, final Object value) {
171        final Object old = get(key);
172        if (httpSession == null) {
173            // create session just-in-time
174            httpSession = request.getSession(true);
175        }
176        httpSession.setAttribute(key, value);
177        return old;
178    }
179
180    @Override
181    public Object remove(final Object key) {
182        final Object old = get(key);
183        if (key instanceof String && httpSession != null) {
184            httpSession.removeAttribute((String) key);
185        }
186        return old;
187    }
188
189    @Override
190    public int size() {
191        int size = 0;
192        if (httpSession != null) {
193            final Enumeration<?> attributes = httpSession.getAttributeNames();
194            while (attributes.hasMoreElements()) {
195                attributes.nextElement();
196                size++;
197            }
198        }
199        return size;
200    }
201
202    @Override
203    public void close() throws IOException {
204        // Nothing to do when using HttpSession
205    }
206}