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    }