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.LinkedHashMap;
024import java.util.Map;
025import java.util.Map.Entry;
026import java.util.Optional;
027import org.jgrapes.core.Channel;
028import org.jgrapes.http.events.DiscardSession;
029import org.jgrapes.http.events.Request;
030
031/**
032 * A in memory session manager. 
033 */
034@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
035public class InMemorySessionManager extends SessionManager {
036
037    @SuppressWarnings({ "serial", "PMD.UseConcurrentHashMap" })
038    private final Map<String, InMemorySession> sessionsById
039        = new LinkedHashMap<>(16, 0.75f, true) {
040
041            @Override
042            protected boolean
043                    removeEldestEntry(Entry<String, InMemorySession> eldest) {
044                if (maxSessions() > 0 && size() > maxSessions()
045                    && eldest.getValue().setBeingDiscarded()) {
046                    fire(new DiscardSession(eldest.getValue()));
047                }
048                return false;
049            }
050        };
051
052    /**
053     * Creates a new session manager with its channel set to
054     * itself and the path set to "/". The manager handles
055     * all {@link Request} events.
056     */
057    public InMemorySessionManager() {
058        this("/");
059    }
060
061    /**
062     * Creates a new session manager with its channel set to
063     * itself and the path set to the given path. The manager
064     * handles all requests that match the given path, using the
065     * same rules as browsers do for selecting the cookies that
066     * are to be sent.
067     * 
068     * @param path the path
069     */
070    public InMemorySessionManager(String path) {
071        this(Channel.SELF, path);
072    }
073
074    /**
075     * Creates a new session manager with its channel set to
076     * the given channel, the path to "/" and the handler's priority 
077     * to 1000. The manager handles all {@link Request} events.
078     * 
079     * @param componentChannel the component channel
080     */
081    public InMemorySessionManager(Channel componentChannel) {
082        this(componentChannel, "/");
083    }
084
085    /**
086     * Creates a new session manager with the given channel and path.
087     * The manager handles all requests that match the given path, using
088     * the same rules as browsers do for selecting the cookies that
089     * are to be sent. The request handler's priority is set to 1000.
090     *  
091     * @param componentChannel the component channel
092     * @param path the path
093     */
094    public InMemorySessionManager(Channel componentChannel, String path) {
095        this(componentChannel, derivePattern(path), 1000, path);
096    }
097
098    /**
099     * Creates a new session manager using the given channel and path.
100     * The manager handles only requests that match the given pattern.
101     * The {@link Request} handler is registered with the given priority.
102     * 
103     * This constructor can be used if special handling of top level
104     * requests is needed.
105     *
106     * @param componentChannel the component channel
107     * @param pattern the path part of a {@link ResourcePattern}
108     * @param priority the priority
109     * @param path the path
110     */
111    public InMemorySessionManager(Channel componentChannel, String pattern,
112            int priority, String path) {
113        super(componentChannel, pattern, priority, path);
114    }
115
116    @Override
117    @SuppressWarnings({ "PMD.CognitiveComplexity",
118        "PMD.AvoidInstantiatingObjectsInLoops" })
119    protected Optional<Instant> startDiscarding(long absoluteTimeout,
120            long idleTimeout) {
121        synchronized (this) {
122            Instant nextTimout = null;
123            for (InMemorySession session : sessionsById.values()) {
124                if (hasTimedOut(session)) {
125                    if (session.setBeingDiscarded()) {
126                        fire(new DiscardSession(session));
127                    }
128                    continue;
129                }
130                if (absoluteTimeout > 0) {
131                    Instant timesOutAt = session.createdAt()
132                        .plus(Duration.ofMillis(absoluteTimeout));
133                    if (nextTimout == null
134                        || timesOutAt.isBefore(nextTimout)) {
135                        nextTimout = timesOutAt;
136                    }
137                }
138                if (idleTimeout > 0) {
139                    Instant timesOutAt = session.lastUsedAt()
140                        .plus(Duration.ofMillis(idleTimeout));
141                    if (nextTimout == null
142                        || timesOutAt.isBefore(nextTimout)) {
143                        nextTimout = timesOutAt;
144                    }
145                }
146            }
147            return Optional.ofNullable(nextTimout);
148        }
149    }
150
151    @Override
152    protected Session createSession(String sessionId) {
153        InMemorySession session = new InMemorySession(sessionId);
154        synchronized (this) {
155            sessionsById.put(sessionId, session);
156        }
157        return session;
158    }
159
160    @Override
161    protected Optional<Session> lookupSession(String sessionId) {
162        synchronized (this) {
163            return Optional.ofNullable(sessionsById.get(sessionId));
164        }
165    }
166
167    @Override
168    protected void removeSession(String sessionId) {
169        synchronized (this) {
170            sessionsById.remove(sessionId);
171        }
172    }
173
174    /*
175     * (non-Javadoc)
176     * 
177     * @see org.jgrapes.http.SessionManager#sessionCount()
178     */
179    @Override
180    protected int sessionCount() {
181        synchronized (this) {
182            return sessionsById.size();
183        }
184    }
185
186}