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}