001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.session.mgt;
020
021 import org.apache.shiro.cache.CacheManager;
022 import org.apache.shiro.cache.CacheManagerAware;
023 import org.apache.shiro.session.Session;
024 import org.apache.shiro.session.UnknownSessionException;
025 import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
026 import org.apache.shiro.session.mgt.eis.SessionDAO;
027 import org.slf4j.Logger;
028 import org.slf4j.LoggerFactory;
029
030 import java.io.Serializable;
031 import java.util.Collection;
032 import java.util.Collections;
033 import java.util.Date;
034
035 /**
036 * Default business-tier implementation of a {@link ValidatingSessionManager}. All session CRUD operations are
037 * delegated to an internal {@link SessionDAO}.
038 *
039 * @author Les Hazlewood
040 * @since 0.1
041 */
042 public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
043
044 //TODO - complete JavaDoc
045
046 private static final Logger log = LoggerFactory.getLogger(DefaultSessionManager.class);
047
048 private SessionFactory sessionFactory;
049
050 protected SessionDAO sessionDAO; //todo - move SessionDAO up to AbstractValidatingSessionManager?
051
052 private CacheManager cacheManager;
053
054 private boolean deleteInvalidSessions;
055
056 public DefaultSessionManager() {
057 this.deleteInvalidSessions = true;
058 this.sessionFactory = new SimpleSessionFactory();
059 this.sessionDAO = new MemorySessionDAO();
060 }
061
062 public void setSessionDAO(SessionDAO sessionDAO) {
063 this.sessionDAO = sessionDAO;
064 applyCacheManagerToSessionDAO();
065 }
066
067 public SessionDAO getSessionDAO() {
068 return this.sessionDAO;
069 }
070
071 /**
072 * Returns the {@code SessionFactory} used to generate new {@link Session} instances. The default instance
073 * is a {@link SimpleSessionFactory}.
074 *
075 * @return the {@code SessionFactory} used to generate new {@link Session} instances.
076 * @since 1.0
077 */
078 public SessionFactory getSessionFactory() {
079 return sessionFactory;
080 }
081
082 /**
083 * Sets the {@code SessionFactory} used to generate new {@link Session} instances. The default instance
084 * is a {@link SimpleSessionFactory}.
085 *
086 * @param sessionFactory the {@code SessionFactory} used to generate new {@link Session} instances.
087 * @since 1.0
088 */
089 public void setSessionFactory(SessionFactory sessionFactory) {
090 this.sessionFactory = sessionFactory;
091 }
092
093 /**
094 * Returns {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
095 * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control. The
096 * default is {@code true} to ensure no orphans exist in the underlying data store.
097 * <h4>Usage</h4>
098 * It is ok to set this to {@code false} <b><em>ONLY</em></b> if you have some other process that you manage yourself
099 * that periodically deletes invalid sessions from the backing data store over time, such as via a Quartz or Cron
100 * job. If you do not do this, the invalid sessions will become 'orphans' and fill up the data store over time.
101 * <p/>
102 * This property is provided because some systems need the ability to perform querying/reporting against sessions in
103 * the data store, even after they have stopped or expired. Setting this attribute to {@code false} will allow
104 * such querying, but with the caveat that the application developer/configurer deletes the sessions themselves by
105 * some other means (cron, quartz, etc).
106 *
107 * @return {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
108 * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.
109 * @since 1.0
110 */
111 public boolean isDeleteInvalidSessions() {
112 return deleteInvalidSessions;
113 }
114
115 /**
116 * Sets whether or not sessions should be automatically deleted after they are discovered to be invalid. Default
117 * value is {@code true} to ensure no orphans will exist in the underlying data store.
118 * <h4>WARNING</h4>
119 * Only set this value to {@code false} if you are manually going to delete sessions yourself by some process
120 * (quartz, cron, etc) external to Shiro's control. See the
121 * {@link #isDeleteInvalidSessions() isDeleteInvalidSessions()} JavaDoc for more.
122 *
123 * @param deleteInvalidSessions whether or not sessions should be automatically deleted after they are discovered
124 * to be invalid.
125 * @since 1.0
126 */
127 @SuppressWarnings({"UnusedDeclaration"})
128 public void setDeleteInvalidSessions(boolean deleteInvalidSessions) {
129 this.deleteInvalidSessions = deleteInvalidSessions;
130 }
131
132 public void setCacheManager(CacheManager cacheManager) {
133 this.cacheManager = cacheManager;
134 applyCacheManagerToSessionDAO();
135 }
136
137 /**
138 * Sets the internal {@code CacheManager} on the {@code SessionDAO} if it implements the
139 * {@link org.apache.shiro.cache.CacheManagerAware CacheManagerAware} interface.
140 * <p/>
141 * This method is called after setting a cacheManager via the
142 * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) setCacheManager} method <em>em</em> when
143 * setting a {@code SessionDAO} via the {@link #setSessionDAO} method to allow it to be propagated
144 * in either case.
145 *
146 * @since 1.0
147 */
148 private void applyCacheManagerToSessionDAO() {
149 if (this.cacheManager != null && this.sessionDAO != null && this.sessionDAO instanceof CacheManagerAware) {
150 ((CacheManagerAware) this.sessionDAO).setCacheManager(this.cacheManager);
151 }
152 }
153
154 protected Session doCreateSession(SessionContext context) {
155 Session s = newSessionInstance(context);
156 if (log.isTraceEnabled()) {
157 log.trace("Creating session for host {}", s.getHost());
158 }
159 create(s);
160 return s;
161 }
162
163 protected Session newSessionInstance(SessionContext context) {
164 return getSessionFactory().createSession(context);
165 }
166
167 /**
168 * Persists the given session instance to an underlying EIS (Enterprise Information System). This implementation
169 * delegates and calls
170 * <code>this.{@link SessionDAO sessionDAO}.{@link SessionDAO#create(org.apache.shiro.session.Session) create}(session);<code>
171 *
172 * @param session the Session instance to persist to the underlying EIS.
173 */
174 protected void create(Session session) {
175 if (log.isDebugEnabled()) {
176 log.debug("Creating new EIS record for new session instance [" + session + "]");
177 }
178 sessionDAO.create(session);
179 }
180
181 @Override
182 protected void onStop(Session session) {
183 if (session instanceof SimpleSession) {
184 SimpleSession ss = (SimpleSession) session;
185 Date stopTs = ss.getStopTimestamp();
186 ss.setLastAccessTime(stopTs);
187 }
188 onChange(session);
189 }
190
191 @Override
192 protected void afterStopped(Session session) {
193 if (isDeleteInvalidSessions()) {
194 delete(session);
195 }
196 }
197
198 protected void onExpiration(Session session) {
199 if (session instanceof SimpleSession) {
200 ((SimpleSession) session).setExpired(true);
201 }
202 onChange(session);
203 }
204
205 @Override
206 protected void afterExpired(Session session) {
207 if (isDeleteInvalidSessions()) {
208 delete(session);
209 }
210 }
211
212 protected void onChange(Session session) {
213 sessionDAO.update(session);
214 }
215
216 protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
217 Serializable sessionId = getSessionId(sessionKey);
218 if (sessionId == null) {
219 log.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a " +
220 "session could not be found.", sessionKey);
221 return null;
222 }
223 Session s = retrieveSessionFromDataSource(sessionId);
224 if (s == null) {
225 //session ID was provided, meaning one is expected to be found, but we couldn't find one:
226 String msg = "Could not find session with ID [" + sessionId + "]";
227 throw new UnknownSessionException(msg);
228 }
229 return s;
230 }
231
232 protected Serializable getSessionId(SessionKey sessionKey) {
233 return sessionKey.getSessionId();
234 }
235
236 protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
237 return sessionDAO.readSession(sessionId);
238 }
239
240 protected void delete(Session session) {
241 sessionDAO.delete(session);
242 }
243
244 protected Collection<Session> getActiveSessions() {
245 Collection<Session> active = sessionDAO.getActiveSessions();
246 return active != null ? active : Collections.<Session>emptySet();
247 }
248
249 }