1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.shiro.session.mgt;
20
21 import org.apache.shiro.cache.CacheManager;
22 import org.apache.shiro.cache.CacheManagerAware;
23 import org.apache.shiro.session.Session;
24 import org.apache.shiro.session.UnknownSessionException;
25 import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
26 import org.apache.shiro.session.mgt.eis.SessionDAO;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 import java.io.Serializable;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.Date;
34
35 /**
36 * Default business-tier implementation of a {@link ValidatingSessionManager}. All session CRUD operations are
37 * delegated to an internal {@link SessionDAO}.
38 *
39 * @since 0.1
40 */
41 public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
42
43 private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSessionManager.class);
44
45 //todo - move SessionDAO up to AbstractValidatingSessionManager?
46 protected SessionDAO sessionDAO;
47
48
49 private SessionFactory sessionFactory;
50
51 private CacheManager cacheManager;
52
53 private boolean deleteInvalidSessions;
54
55 public DefaultSessionManager() {
56 this.deleteInvalidSessions = true;
57 this.sessionFactory = new SimpleSessionFactory();
58 this.sessionDAO = new MemorySessionDAO();
59 }
60
61 public void setSessionDAO(SessionDAO sessionDAO) {
62 this.sessionDAO = sessionDAO;
63 applyCacheManagerToSessionDAO();
64 }
65
66 public SessionDAO getSessionDAO() {
67 return this.sessionDAO;
68 }
69
70 /**
71 * Returns the {@code SessionFactory} used to generate new {@link Session} instances. The default instance
72 * is a {@link SimpleSessionFactory}.
73 *
74 * @return the {@code SessionFactory} used to generate new {@link Session} instances.
75 * @since 1.0
76 */
77 public SessionFactory getSessionFactory() {
78 return sessionFactory;
79 }
80
81 /**
82 * Sets the {@code SessionFactory} used to generate new {@link Session} instances. The default instance
83 * is a {@link SimpleSessionFactory}.
84 *
85 * @param sessionFactory the {@code SessionFactory} used to generate new {@link Session} instances.
86 * @since 1.0
87 */
88 public void setSessionFactory(SessionFactory sessionFactory) {
89 this.sessionFactory = sessionFactory;
90 }
91
92 /**
93 * Returns {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
94 * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control. The
95 * default is {@code true} to ensure no orphans exist in the underlying data store.
96 * <h4>Usage</h4>
97 * It is ok to set this to {@code false} <b><em>ONLY</em></b> if you have some other process that you manage yourself
98 * that periodically deletes invalid sessions from the backing data store over time, such as via a Quartz or Cron
99 * job. If you do not do this, the invalid sessions will become 'orphans' and fill up the data store over time.
100 * <p/>
101 * This property is provided because some systems need the ability to perform querying/reporting against sessions in
102 * the data store, even after they have stopped or expired. Setting this attribute to {@code false} will allow
103 * such querying, but with the caveat that the application developer/configurer deletes the sessions themselves by
104 * some other means (cron, quartz, etc.).
105 *
106 * @return {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
107 * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.
108 * @since 1.0
109 */
110 public boolean isDeleteInvalidSessions() {
111 return deleteInvalidSessions;
112 }
113
114 /**
115 * Sets whether or not sessions should be automatically deleted after they are discovered to be invalid. Default
116 * value is {@code true} to ensure no orphans will exist in the underlying data store.
117 * <h4>WARNING</h4>
118 * Only set this value to {@code false} if you are manually going to delete sessions yourself by some process
119 * (quartz, cron, etc.) external to Shiro's control. See the
120 * {@link #isDeleteInvalidSessions() isDeleteInvalidSessions()} JavaDoc for more.
121 *
122 * @param deleteInvalidSessions whether or not sessions should be automatically deleted after they are discovered
123 * to be invalid.
124 * @since 1.0
125 */
126 @SuppressWarnings({"UnusedDeclaration"})
127 public void setDeleteInvalidSessions(boolean deleteInvalidSessions) {
128 this.deleteInvalidSessions = deleteInvalidSessions;
129 }
130
131 public void setCacheManager(CacheManager cacheManager) {
132 this.cacheManager = cacheManager;
133 applyCacheManagerToSessionDAO();
134 }
135
136 /**
137 * Sets the internal {@code CacheManager} on the {@code SessionDAO} if it implements the
138 * {@link org.apache.shiro.cache.CacheManagerAware CacheManagerAware} interface.
139 * <p/>
140 * This method is called after setting a cacheManager via the
141 * {@link #setCacheManager(org.apache.shiro.cache.CacheManager) setCacheManager} method <em>em</em> when
142 * setting a {@code SessionDAO} via the {@link #setSessionDAO} method to allow it to be propagated
143 * in either case.
144 *
145 * @since 1.0
146 */
147 private void applyCacheManagerToSessionDAO() {
148 if (this.cacheManager != null && this.sessionDAO != null && this.sessionDAO instanceof CacheManagerAware) {
149 ((CacheManagerAware) this.sessionDAO).setCacheManager(this.cacheManager);
150 }
151 }
152
153 protected Session doCreateSession(SessionContext context) {
154 Session s = newSessionInstance(context);
155 if (LOGGER.isTraceEnabled()) {
156 LOGGER.trace("Creating session for host {}", s.getHost());
157 }
158 create(s);
159 return s;
160 }
161
162 protected Session newSessionInstance(SessionContext context) {
163 return getSessionFactory().createSession(context);
164 }
165
166 /**
167 * Persists the given session instance to an underlying EIS (Enterprise Information System). This implementation
168 * delegates and calls
169 * <code>this.{@link SessionDAO sessionDAO}.{@link SessionDAO#create(org.apache.shiro.session.Session) create}(session);<code>
170 *
171 * @param session the Session instance to persist to the underlying EIS.
172 */
173 protected void create(Session session) {
174 if (LOGGER.isDebugEnabled()) {
175 LOGGER.debug("Creating new EIS record for new session instance [" + session + "]");
176 }
177 sessionDAO.create(session);
178 }
179
180 @Override
181 protected void onStop(Session session) {
182 if (session instanceof SimpleSession) {
183 SimpleSession ss = (SimpleSession) session;
184 Date stopTs = ss.getStopTimestamp();
185 ss.setLastAccessTime(stopTs);
186 }
187 onChange(session);
188 }
189
190 @Override
191 protected void afterStopped(Session session) {
192 if (isDeleteInvalidSessions()) {
193 delete(session);
194 }
195 }
196
197 protected void onExpiration(Session session) {
198 if (session instanceof SimpleSession) {
199 ((SimpleSession) session).setExpired(true);
200 }
201 onChange(session);
202 }
203
204 @Override
205 protected void afterExpired(Session session) {
206 if (isDeleteInvalidSessions()) {
207 delete(session);
208 }
209 }
210
211 protected void onChange(Session session) {
212 sessionDAO.update(session);
213 }
214
215 protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
216 Serializable sessionId = getSessionId(sessionKey);
217 if (sessionId == null) {
218 LOGGER.debug("Unable to resolve session ID from SessionKey [{}]. Returning null to indicate a "
219 + "session could not be found.", sessionKey);
220 return null;
221 }
222 Session s = retrieveSessionFromDataSource(sessionId);
223 if (s == null) {
224 //session ID was provided, meaning one is expected to be found, but we couldn't find one:
225 String msg = "Could not find session with ID [" + sessionId + "]";
226 throw new UnknownSessionException(msg);
227 }
228 return s;
229 }
230
231 protected Serializable getSessionId(SessionKey sessionKey) {
232 return sessionKey.getSessionId();
233 }
234
235 protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
236 return sessionDAO.readSession(sessionId);
237 }
238
239 protected void delete(Session session) {
240 sessionDAO.delete(session);
241 }
242
243 protected Collection<Session> getActiveSessions() {
244 Collection<Session> active = sessionDAO.getActiveSessions();
245 return active != null ? active : Collections.<Session>emptySet();
246 }
247
248 }