001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2017-2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.http;
020
021import java.time.Duration;
022import java.time.Instant;
023import java.util.Iterator;
024import java.util.LinkedHashMap;
025import java.util.Map;
026import java.util.Map.Entry;
027import java.util.Optional;
028import org.jgrapes.core.Channel;
029import org.jgrapes.http.events.Request;
030
031/**
032 * A in memory session manager. 
033 */
034public class InMemorySessionManager extends SessionManager {
035
036    @SuppressWarnings({ "serial", "PMD.UseConcurrentHashMap" })
037    private final Map<String, Session> sessionsById
038        = new LinkedHashMap<String, Session>(16, 0.75f, true) {
039
040            @Override
041            protected boolean removeEldestEntry(Entry<String, Session> eldest) {
042                return maxSessions() > 0 && size() > maxSessions();
043            }
044        };
045
046    /**
047     * Creates a new session manager with its channel set to
048     * itself and the path set to "/". The manager handles
049     * all {@link Request} events.
050     */
051    public InMemorySessionManager() {
052        this("/");
053    }
054
055    /**
056     * Creates a new session manager with its channel set to
057     * itself and the path set to the given path. The manager
058     * handles all requests that match the given path, using the
059     * same rules as browsers do for selecting the cookies that
060     * are to be sent.
061     * 
062     * @param path the path
063     */
064    public InMemorySessionManager(String path) {
065        this(Channel.SELF, path);
066    }
067
068    /**
069     * Creates a new session manager with its channel set to
070     * the given channel and the path to "/". The manager handles
071     * all {@link Request} events.
072     * 
073     * @param componentChannel the component channel
074     */
075    public InMemorySessionManager(Channel componentChannel) {
076        this(componentChannel, "/");
077    }
078
079    /**
080     * Creates a new session manager with the given channel and path.
081     * The manager handles all requests that match the given path, using
082     * the same rules as browsers do for selecting the cookies that
083     * are to be sent.
084     *  
085     * @param componentChannel the component channel
086     * @param path the path
087     */
088    public InMemorySessionManager(Channel componentChannel, String path) {
089        this(componentChannel, derivePattern(path), 1000, path);
090    }
091
092    /**
093     * Creates a new session manager using the given channel and path.
094     * The manager handles only requests that match the given pattern.
095     * The {@link Request} handler is registered with the given priority.
096     * 
097     * This constructor can be used if special handling of top level
098     * requests is needed.
099     *
100     * @param componentChannel the component channel
101     * @param pattern the path part of a {@link ResourcePattern}
102     * @param priority the priority
103     * @param path the path
104     */
105    public InMemorySessionManager(Channel componentChannel, String pattern,
106            int priority, String path) {
107        super(componentChannel, pattern, priority, path);
108    }
109
110    @Override
111    @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
112    protected Session createSession(String sessionId) {
113        Session session = new InMemorySession(sessionId);
114        Instant now = Instant.now();
115        synchronized (this) {
116            if (absoluteTimeout() > 0) {
117                // Quick check for sessions that are too old
118                for (Iterator<Entry<String, Session>> iter = sessionsById
119                    .entrySet().iterator(); iter.hasNext();) {
120                    Entry<String, Session> entry = iter.next();
121                    if (Duration.between(entry.getValue().createdAt(), now)
122                        .getSeconds() > absoluteTimeout()) {
123                        iter.remove();
124                    } else {
125                        break;
126                    }
127                }
128            }
129            if (sessionsById.size() >= maxSessions() && absoluteTimeout() > 0) {
130                // Thorough search for sessions that are too old
131                sessionsById.entrySet().removeIf(entry -> {
132                    return Duration.between(entry.getValue().createdAt(),
133                        now).getSeconds() > absoluteTimeout()
134                        || Duration.between(entry.getValue().lastUsedAt(),
135                            now).getSeconds() > idleTimeout();
136                });
137            }
138            sessionsById.put(sessionId, session);
139        }
140        return session;
141    }
142
143    @Override
144    protected Optional<Session> lookupSession(String sessionId) {
145        return Optional.ofNullable(sessionsById.get(sessionId));
146    }
147
148    @Override
149    protected void removeSession(String sessionId) {
150        synchronized (this) {
151            sessionsById.remove(sessionId);
152        }
153    }
154
155    /*
156     * (non-Javadoc)
157     * 
158     * @see org.jgrapes.http.SessionManager#sessionCount()
159     */
160    @Override
161    protected int sessionCount() {
162        return sessionsById.size();
163    }
164
165}