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.authz.AuthorizationException;
022    import org.apache.shiro.session.ExpiredSessionException;
023    import org.apache.shiro.session.InvalidSessionException;
024    import org.apache.shiro.session.Session;
025    import org.apache.shiro.session.UnknownSessionException;
026    import org.apache.shiro.util.Destroyable;
027    import org.apache.shiro.util.LifecycleUtils;
028    import org.slf4j.Logger;
029    import org.slf4j.LoggerFactory;
030    
031    import java.util.Collection;
032    
033    
034    /**
035     * Default business-tier implementation of the {@link ValidatingSessionManager} interface.
036     *
037     * @author Les Hazlewood
038     * @author Jeremy Haile
039     * @since 0.1
040     */
041    public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager
042            implements ValidatingSessionManager, Destroyable {
043    
044        //TODO - complete JavaDoc
045    
046        private static final Logger log = LoggerFactory.getLogger(AbstractValidatingSessionManager.class);
047    
048        /**
049         * The default interval at which sessions will be validated (1 hour);
050         * This can be overridden by calling {@link #setSessionValidationInterval(long)}
051         */
052        public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = MILLIS_PER_HOUR;
053    
054        protected boolean sessionValidationSchedulerEnabled;
055    
056        /**
057         * Scheduler used to validate sessions on a regular basis.
058         */
059        protected SessionValidationScheduler sessionValidationScheduler;
060    
061        protected long sessionValidationInterval;
062    
063        public AbstractValidatingSessionManager() {
064            this.sessionValidationSchedulerEnabled = true;
065            this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
066        }
067    
068        public boolean isSessionValidationSchedulerEnabled() {
069            return sessionValidationSchedulerEnabled;
070        }
071    
072        @SuppressWarnings({"UnusedDeclaration"})
073        public void setSessionValidationSchedulerEnabled(boolean sessionValidationSchedulerEnabled) {
074            this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled;
075        }
076    
077        public void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) {
078            this.sessionValidationScheduler = sessionValidationScheduler;
079        }
080    
081        public SessionValidationScheduler getSessionValidationScheduler() {
082            return sessionValidationScheduler;
083        }
084    
085        private void enableSessionValidationIfNecessary() {
086            SessionValidationScheduler scheduler = getSessionValidationScheduler();
087            if (isSessionValidationSchedulerEnabled() && (scheduler == null || !scheduler.isEnabled())) {
088                enableSessionValidation();
089            }
090        }
091    
092        /**
093         * If using the underlying default <tt>SessionValidationScheduler</tt> (that is, the
094         * {@link #setSessionValidationScheduler(SessionValidationScheduler) setSessionValidationScheduler} method is
095         * never called) , this method allows one to specify how
096         * frequently session should be validated (to check for orphans).  The default value is
097         * {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
098         * <p/>
099         * If you override the default scheduler, it is assumed that overriding instance 'knows' how often to
100         * validate sessions, and this attribute will be ignored.
101         * <p/>
102         * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
103         *
104         * @param sessionValidationInterval the time in milliseconds between checking for valid sessions to reap orphans.
105         */
106        public void setSessionValidationInterval(long sessionValidationInterval) {
107            this.sessionValidationInterval = sessionValidationInterval;
108        }
109    
110        public long getSessionValidationInterval() {
111            return sessionValidationInterval;
112        }
113    
114        @Override
115        protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
116            enableSessionValidationIfNecessary();
117    
118            log.trace("Attempting to retrieve session with key {}", key);
119    
120            Session s = retrieveSession(key);
121            if (s != null) {
122                validate(s, key);
123            }
124            return s;
125        }
126    
127        /**
128         * Looks up a session from the underlying data store based on the specified session key.
129         *
130         * @param key the session key to use to look up the target session.
131         * @return the session identified by {@code sessionId}.
132         * @throws UnknownSessionException if there is no session identified by {@code sessionId}.
133         */
134        protected abstract Session retrieveSession(SessionKey key) throws UnknownSessionException;
135    
136        protected Session createSession(SessionContext context) throws AuthorizationException {
137            enableSessionValidationIfNecessary();
138            return doCreateSession(context);
139        }
140    
141        protected abstract Session doCreateSession(SessionContext initData) throws AuthorizationException;
142    
143        protected void validate(Session session, SessionKey key) throws InvalidSessionException {
144            try {
145                doValidate(session);
146            } catch (ExpiredSessionException ese) {
147                onExpiration(session, ese, key);
148                throw ese;
149            } catch (InvalidSessionException ise) {
150                onInvalidation(session, ise, key);
151                throw ise;
152            }
153        }
154    
155        protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
156            log.trace("Session with id [{}] has expired.", s.getId());
157            onExpiration(s);
158            notifyExpiration(s);
159            afterExpired(s);
160        }
161    
162        protected void onExpiration(Session session) {
163            onChange(session);
164        }
165    
166        protected void afterExpired(Session session) {
167        }
168    
169        protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) {
170            if (ise instanceof ExpiredSessionException) {
171                onExpiration(s, (ExpiredSessionException) ise, key);
172                return;
173            }
174            log.trace("Session with id [{}] is invalid.", s.getId());
175            onStop(s);
176            notifyStop(s);
177            afterStopped(s);
178        }
179    
180        protected void doValidate(Session session) throws InvalidSessionException {
181            if (session instanceof ValidatingSession) {
182                ((ValidatingSession) session).validate();
183            } else {
184                String msg = "The " + getClass().getName() + " implementation only supports validating " +
185                        "Session implementations of the " + ValidatingSession.class.getName() + " interface.  " +
186                        "Please either implement this interface in your session implementation or override the " +
187                        AbstractValidatingSessionManager.class.getName() + ".doValidate(Session) method to perform validation.";
188                throw new IllegalStateException(msg);
189            }
190        }
191    
192        /**
193         * Subclass template hook in case per-session timeout is not based on
194         * {@link org.apache.shiro.session.Session#getTimeout()}.
195         * <p/>
196         * <p>This implementation merely returns {@link org.apache.shiro.session.Session#getTimeout()}</p>
197         *
198         * @param session the session for which to determine session timeout.
199         * @return the time in milliseconds the specified session may remain idle before expiring.
200         */
201        protected long getTimeout(Session session) {
202            return session.getTimeout();
203        }
204    
205        protected SessionValidationScheduler createSessionValidationScheduler() {
206            ExecutorServiceSessionValidationScheduler scheduler;
207    
208            if (log.isDebugEnabled()) {
209                log.debug("No sessionValidationScheduler set.  Attempting to create default instance.");
210            }
211            scheduler = new ExecutorServiceSessionValidationScheduler(this);
212            scheduler.setInterval(getSessionValidationInterval());
213            if (log.isTraceEnabled()) {
214                log.trace("Created default SessionValidationScheduler instance of type [" + scheduler.getClass().getName() + "].");
215            }
216            return scheduler;
217        }
218    
219        protected void enableSessionValidation() {
220            SessionValidationScheduler scheduler = getSessionValidationScheduler();
221            if (scheduler == null) {
222                scheduler = createSessionValidationScheduler();
223                setSessionValidationScheduler(scheduler);
224            }
225            if (log.isInfoEnabled()) {
226                log.info("Enabling session validation scheduler...");
227            }
228            scheduler.enableSessionValidation();
229            afterSessionValidationEnabled();
230        }
231    
232        protected void afterSessionValidationEnabled() {
233        }
234    
235        protected void disableSessionValidation() {
236            beforeSessionValidationDisabled();
237            SessionValidationScheduler scheduler = getSessionValidationScheduler();
238            if (scheduler != null) {
239                try {
240                    scheduler.disableSessionValidation();
241                    if (log.isInfoEnabled()) {
242                        log.info("Disabled session validation scheduler.");
243                    }
244                } catch (Exception e) {
245                    if (log.isDebugEnabled()) {
246                        String msg = "Unable to disable SessionValidationScheduler.  Ignoring (shutting down)...";
247                        log.debug(msg, e);
248                    }
249                }
250                LifecycleUtils.destroy(scheduler);
251                setSessionValidationScheduler(null);
252            }
253        }
254    
255        protected void beforeSessionValidationDisabled() {
256        }
257    
258        public void destroy() {
259            disableSessionValidation();
260        }
261    
262        /**
263         * @see ValidatingSessionManager#validateSessions()
264         */
265        public void validateSessions() {
266            if (log.isInfoEnabled()) {
267                log.info("Validating all active sessions...");
268            }
269    
270            int invalidCount = 0;
271    
272            Collection<Session> activeSessions = getActiveSessions();
273    
274            if (activeSessions != null && !activeSessions.isEmpty()) {
275                for (Session s : activeSessions) {
276                    try {
277                        doValidate(s);
278                    } catch (InvalidSessionException e) {
279                        if (log.isDebugEnabled()) {
280                            boolean expired = (e instanceof ExpiredSessionException);
281                            String msg = "Invalidated session with id [" + s.getId() + "]" +
282                                    (expired ? " (expired)" : " (stopped)");
283                            log.debug(msg);
284                        }
285                        invalidCount++;
286                    }
287                }
288            }
289    
290            if (log.isInfoEnabled()) {
291                String msg = "Finished session validation.";
292                if (invalidCount > 0) {
293                    msg += "  [" + invalidCount + "] sessions were stopped.";
294                } else {
295                    msg += "  No sessions were stopped.";
296                }
297                log.info(msg);
298            }
299        }
300    
301        protected abstract Collection<Session> getActiveSessions();
302    }