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 */
019package org.apache.shiro.session.mgt;
020
021import java.util.concurrent.Executors;
022import java.util.concurrent.ScheduledExecutorService;
023import java.util.concurrent.ThreadFactory;
024import java.util.concurrent.TimeUnit;
025import java.util.concurrent.atomic.AtomicInteger;
026
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030
031/**
032 * SessionValidationScheduler implementation that uses a
033 * {@link ScheduledExecutorService} to call {@link ValidatingSessionManager#validateSessions()} every
034 * <em>{@link #getSessionValidationInterval sessionValidationInterval}</em> milliseconds.
035 *
036 * @since 0.9
037 */
038public class ExecutorServiceSessionValidationScheduler implements SessionValidationScheduler, Runnable {
039
040    //TODO - complete JavaDoc
041
042    /**
043     * Private internal log instance.
044     */
045    private static final Logger LOGGER = LoggerFactory.getLogger(ExecutorServiceSessionValidationScheduler.class);
046
047    ValidatingSessionManager sessionManager;
048    private ScheduledExecutorService service;
049    private long sessionValidationInterval = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
050    private boolean enabled;
051    private String threadNamePrefix = "SessionValidationThread-";
052
053    public ExecutorServiceSessionValidationScheduler() {
054        super();
055    }
056
057    public ExecutorServiceSessionValidationScheduler(ValidatingSessionManager sessionManager) {
058        this.sessionManager = sessionManager;
059    }
060
061    public ValidatingSessionManager getSessionManager() {
062        return sessionManager;
063    }
064
065    public void setSessionManager(ValidatingSessionManager sessionManager) {
066        this.sessionManager = sessionManager;
067    }
068
069    public long getSessionValidationInterval() {
070        return sessionValidationInterval;
071    }
072
073    public void setSessionValidationInterval(long sessionValidationInterval) {
074        this.sessionValidationInterval = sessionValidationInterval;
075    }
076
077    public boolean isEnabled() {
078        return this.enabled;
079    }
080
081    public void setThreadNamePrefix(String threadNamePrefix) {
082        this.threadNamePrefix = threadNamePrefix;
083    }
084
085    public String getThreadNamePrefix() {
086        return this.threadNamePrefix;
087    }
088
089    /**
090     * Creates a single thread {@link ScheduledExecutorService} to validate sessions at fixed intervals
091     * and enables this scheduler. The executor is created as a daemon thread to allow JVM to shut down
092     */
093    //TODO Implement an integration test to test for jvm exit as part of the standalone example
094    // (so we don't have to change the unit test execution model for the core module)
095    public void enableSessionValidation() {
096        if (this.sessionValidationInterval > 0L) {
097            this.service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
098                private final AtomicInteger count = new AtomicInteger(1);
099
100                public Thread newThread(Runnable r) {
101                    Thread thread = new Thread(r);
102                    thread.setDaemon(true);
103                    thread.setName(threadNamePrefix + count.getAndIncrement());
104                    return thread;
105                }
106            });
107            this.service.scheduleAtFixedRate(this, sessionValidationInterval,
108                    sessionValidationInterval, TimeUnit.MILLISECONDS);
109        }
110        this.enabled = true;
111    }
112
113    public void run() {
114        if (LOGGER.isDebugEnabled()) {
115            LOGGER.debug("Executing session validation...");
116        }
117        Thread.currentThread().setUncaughtExceptionHandler((t, e) -> {
118            LOGGER.error("Error while validating the session, the thread will be stopped and session validation disabled", e);
119            this.disableSessionValidation();
120        });
121        long startTime = System.currentTimeMillis();
122        try {
123            this.sessionManager.validateSessions();
124        } catch (RuntimeException e) {
125            LOGGER.error("Error while validating the session", e);
126            //we don't stop the thread
127        }
128        long stopTime = System.currentTimeMillis();
129        if (LOGGER.isDebugEnabled()) {
130            LOGGER.debug("Session validation completed successfully in " + (stopTime - startTime) + " milliseconds.");
131        }
132    }
133
134    public void disableSessionValidation() {
135        if (this.service != null) {
136            this.service.shutdownNow();
137        }
138        this.enabled = false;
139    }
140}