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.quartz;
020
021import org.quartz.JobBuilder;
022import org.quartz.JobDetail;
023import org.quartz.Scheduler;
024import org.quartz.SchedulerException;
025import org.quartz.SimpleTrigger;
026import org.quartz.TriggerBuilder;
027import org.quartz.TriggerKey;
028import org.quartz.impl.StdSchedulerFactory;
029
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.apache.shiro.session.mgt.DefaultSessionManager;
034import org.apache.shiro.session.mgt.SessionValidationScheduler;
035import org.apache.shiro.session.mgt.ValidatingSessionManager;
036
037import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
038
039
040/**
041 * An implementation of the {@link org.apache.shiro.session.mgt.SessionValidationScheduler SessionValidationScheduler} that uses Quartz to schedule a
042 * job to call {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions()} on
043 * a regular basis.
044 *
045 * @since 0.1
046 */
047public class QuartzSessionValidationScheduler implements SessionValidationScheduler {
048
049    //TODO - complete JavaDoc
050
051    /*--------------------------------------------
052    |             C O N S T A N T S             |
053    ============================================*/
054    /**
055     * The default interval at which sessions will be validated (1 hour);
056     * This can be overridden by calling {@link #setSessionValidationInterval(long)}
057     */
058    public static final long DEFAULT_SESSION_VALIDATION_INTERVAL = DefaultSessionManager.DEFAULT_SESSION_VALIDATION_INTERVAL;
059
060    /**
061     * The name assigned to the quartz job.
062     */
063    private static final String JOB_NAME = "SessionValidationJob";
064
065    /*--------------------------------------------
066    |    I N S T A N C E   V A R I A B L E S    |
067    ============================================*/
068    private static final Logger log = LoggerFactory.getLogger(QuartzSessionValidationScheduler.class);
069
070    /**
071     * The configured Quartz scheduler to use to schedule the Quartz job.  If no scheduler is
072     * configured, the scheduler will be retrieved by calling {@link StdSchedulerFactory#getDefaultScheduler()}
073     */
074    private Scheduler scheduler;
075
076    private boolean schedulerImplicitlyCreated = false;
077
078    private boolean enabled = false;
079
080    /**
081     * The session manager used to validate sessions.
082     */
083    private ValidatingSessionManager sessionManager;
084
085    /**
086     * The session validation interval in milliseconds.
087     */
088    private long sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
089
090    /*--------------------------------------------
091    |         C O N S T R U C T O R S           |
092    ============================================*/
093
094    /**
095     * Default constructor.
096     */
097    public QuartzSessionValidationScheduler() {
098    }
099
100    /**
101     * Constructor that specifies the session manager that should be used for validating sessions.
102     *
103     * @param sessionManager the <tt>SessionManager</tt> that should be used to validate sessions.
104     */
105    public QuartzSessionValidationScheduler(ValidatingSessionManager sessionManager) {
106        this.sessionManager = sessionManager;
107    }
108
109    /*--------------------------------------------
110    |  A C C E S S O R S / M O D I F I E R S    |
111    ============================================*/
112
113    protected Scheduler getScheduler() throws SchedulerException {
114        if (scheduler == null) {
115            scheduler = StdSchedulerFactory.getDefaultScheduler();
116            schedulerImplicitlyCreated = true;
117        }
118        return scheduler;
119    }
120
121    public void setScheduler(Scheduler scheduler) {
122        this.scheduler = scheduler;
123    }
124
125    public void setSessionManager(ValidatingSessionManager sessionManager) {
126        this.sessionManager = sessionManager;
127    }
128
129    public boolean isEnabled() {
130        return this.enabled;
131    }
132
133    /**
134     * Specifies how frequently (in milliseconds) this Scheduler will call the
135     * {@link org.apache.shiro.session.mgt.ValidatingSessionManager#validateSessions() ValidatingSessionManager#validateSessions()} method.
136     *
137     * <p>Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
138     *
139     * @param sessionValidationInterval
140     */
141    public void setSessionValidationInterval(long sessionValidationInterval) {
142        this.sessionValidationInterval = sessionValidationInterval;
143    }
144
145    /*--------------------------------------------
146    |               M E T H O D S               |
147    ============================================*/
148
149    /**
150     * Starts session validation by creating a Quartz simple trigger, linking it to
151     * the {@link QuartzSessionValidationJob}, and scheduling it with the Quartz scheduler.
152     */
153    public void enableSessionValidation() {
154
155        if (log.isDebugEnabled()) {
156            log.debug("Scheduling session validation job using Quartz with " +
157                    "session validation interval of [" + sessionValidationInterval + "]ms...");
158        }
159
160        try {
161            TriggerBuilder<SimpleTrigger> triggerBuilder =
162                TriggerBuilder.newTrigger()
163                    .withIdentity(getClass().getName(), Scheduler.DEFAULT_GROUP)
164                    .withSchedule(simpleSchedule()
165                        .withIntervalInMilliseconds(sessionValidationInterval)
166                        .withRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY));
167            SimpleTrigger trigger = triggerBuilder.build();
168
169            JobDetail detail = JobBuilder.newJob(QuartzSessionValidationJob.class)
170                        .withIdentity(JOB_NAME, Scheduler.DEFAULT_GROUP).build();
171            detail.getJobDataMap().put(QuartzSessionValidationJob.SESSION_MANAGER_KEY, sessionManager);
172
173            Scheduler scheduler = getScheduler();
174
175            scheduler.scheduleJob(detail, trigger);
176            if (schedulerImplicitlyCreated) {
177                scheduler.start();
178                if (log.isDebugEnabled()) {
179                    log.debug("Successfully started implicitly created Quartz Scheduler instance.");
180                }
181            }
182            this.enabled = true;
183
184            if (log.isDebugEnabled()) {
185                log.debug("Session validation job successfully scheduled with Quartz.");
186            }
187
188        } catch (SchedulerException e) {
189            if (log.isErrorEnabled()) {
190                log.error("Error starting the Quartz session validation job.  Session validation may not occur.", e);
191            }
192        }
193    }
194
195    public void disableSessionValidation() {
196        if (log.isDebugEnabled()) {
197            log.debug("Stopping Quartz session validation job...");
198        }
199
200        Scheduler scheduler;
201        try {
202            scheduler = getScheduler();
203            if (scheduler == null) {
204                if (log.isWarnEnabled()) {
205                    log.warn("getScheduler() method returned a null Quartz scheduler, which is unexpected.  Please " +
206                            "check your configuration and/or implementation.  Returning quietly since there is no " +
207                            "validation job to remove (scheduler does not exist).");
208                }
209                return;
210            }
211        } catch (SchedulerException e) {
212            if (log.isWarnEnabled()) {
213                log.warn("Unable to acquire Quartz Scheduler.  Ignoring and returning (already stopped?)", e);
214            }
215            return;
216        }
217
218        try {
219            scheduler.unscheduleJob(TriggerKey.triggerKey(JOB_NAME, Scheduler.DEFAULT_GROUP));
220            if (log.isDebugEnabled()) {
221                log.debug("Quartz session validation job stopped successfully.");
222            }
223        } catch (SchedulerException e) {
224            if (log.isDebugEnabled()) {
225                log.debug("Could not cleanly remove SessionValidationJob from Quartz scheduler.  " +
226                        "Ignoring and stopping.", e);
227            }
228        }
229
230        this.enabled = false;
231
232        if (schedulerImplicitlyCreated) {
233            try {
234                scheduler.shutdown();
235            } catch (SchedulerException e) {
236                if (log.isWarnEnabled()) {
237                    log.warn("Unable to cleanly shutdown implicitly created Quartz Scheduler instance.", e);
238                }
239            } finally {
240                setScheduler(null);
241                schedulerImplicitlyCreated = false;
242            }
243        }
244
245
246    }
247}