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 module hunt.shiro.session.mgt.AbstractValidatingSessionManager;
20 
21 import hunt.shiro.session.mgt.AbstractNativeSessionManager;
22 import hunt.shiro.session.mgt.DefaultSessionKey;
23 import hunt.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
24 import hunt.shiro.session.mgt.SessionContext;
25 import hunt.shiro.session.mgt.SessionKey;
26 import hunt.shiro.session.mgt.SessionValidationScheduler;
27 import hunt.shiro.session.mgt.ValidatingSessionManager;
28 import hunt.shiro.session.mgt.ValidatingSession;
29 
30 
31 import hunt.shiro.Exceptions;
32 import hunt.shiro.session.Session;
33 import hunt.shiro.util.Common;
34 import hunt.shiro.util.LifecycleUtils;
35 
36 import hunt.Exceptions;
37 import hunt.collection;
38 import hunt.logging.Logger;
39 
40 import std.conv;
41 
42 
43 /**
44  * Default business-tier implementation of the {@link ValidatingSessionManager} interface.
45  *
46  */
47 abstract class AbstractValidatingSessionManager : AbstractNativeSessionManager,
48         ValidatingSessionManager, Destroyable {
49 
50 
51     /**
52      * The default interval at which sessions will be validated (1 hour);
53      * This can be overridden by calling {@link #setSessionValidationInterval(long)}
54      */
55     enum DEFAULT_SESSION_VALIDATION_INTERVAL = MILLIS_PER_HOUR;
56 
57     protected bool sessionValidationSchedulerEnabled;
58 
59     /**
60      * Scheduler used to validate sessions on a regular basis.
61      */
62     protected SessionValidationScheduler sessionValidationScheduler;
63 
64     protected long sessionValidationInterval;
65 
66     this() {
67         this.sessionValidationSchedulerEnabled = true;
68         this.sessionValidationInterval = DEFAULT_SESSION_VALIDATION_INTERVAL;
69     }
70 
71     bool isSessionValidationSchedulerEnabled() {
72         return sessionValidationSchedulerEnabled;
73     }
74 
75     //@SuppressWarnings({"UnusedDeclaration"})
76      void setSessionValidationSchedulerEnabled(bool sessionValidationSchedulerEnabled) {
77         this.sessionValidationSchedulerEnabled = sessionValidationSchedulerEnabled;
78     }
79 
80      void setSessionValidationScheduler(SessionValidationScheduler sessionValidationScheduler) {
81         this.sessionValidationScheduler = sessionValidationScheduler;
82     }
83 
84      SessionValidationScheduler getSessionValidationScheduler() {
85         return sessionValidationScheduler;
86     }
87 
88     private void enableSessionValidationIfNecessary() {
89         SessionValidationScheduler scheduler = getSessionValidationScheduler();
90         if (isSessionValidationSchedulerEnabled() && (scheduler  is null || !scheduler.isEnabled())) {
91             enableSessionValidation();
92         }
93     }
94 
95     /**
96      * If using the underlying default <tt>SessionValidationScheduler</tt> (that is, the
97      * {@link #setSessionValidationScheduler(SessionValidationScheduler) setSessionValidationScheduler} method is
98      * never called) , this method allows one to specify how
99      * frequently session should be validated (to check for orphans).  The default value is
100      * {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
101      * <p/>
102      * If you override the default scheduler, it is assumed that overriding instance 'knows' how often to
103      * validate sessions, and this attribute will be ignored.
104      * <p/>
105      * Unless this method is called, the default value is {@link #DEFAULT_SESSION_VALIDATION_INTERVAL}.
106      *
107      * @param sessionValidationInterval the time in milliseconds between checking for valid sessions to reap orphans.
108      */
109      void setSessionValidationInterval(long sessionValidationInterval) {
110         this.sessionValidationInterval = sessionValidationInterval;
111     }
112 
113      long getSessionValidationInterval() {
114         return sessionValidationInterval;
115     }
116 
117     override
118     protected final Session doGetSession(SessionKey key){
119         enableSessionValidationIfNecessary();
120 
121         // tracef("Attempting to retrieve session with key %s", key);
122 
123         Session s = retrieveSession(key);
124         if (s !is null) {
125             validate(s, key);
126         }
127         return s;
128     }
129 
130     /**
131      * Looks up a session from the underlying data store based on the specified session key.
132      *
133      * @param key the session key to use to look up the target session.
134      * @return the session identified by {@code sessionId}.
135      * @throws UnknownSessionException if there is no session identified by {@code sessionId}.
136      */
137     protected abstract Session retrieveSession(SessionKey key);
138 
139     override protected Session createSession(SessionContext context){
140         enableSessionValidationIfNecessary();
141         return doCreateSession(context);
142     }
143 
144     protected abstract Session doCreateSession(SessionContext initData);
145 
146     protected void validate(Session session, SessionKey key){
147         try {
148             doValidate(session);
149         } catch (ExpiredSessionException ese) {
150             onExpiration(session, ese, key);
151             throw ese;
152         } catch (InvalidSessionException ise) {
153             onInvalidation(session, ise, key);
154             throw ise;
155         }
156     }
157 
158     protected void onExpiration(Session s, ExpiredSessionException ese, SessionKey key) {
159         version(HUT_DEBUG) warningf("Session with id [%s] has expired.", s.getId());
160         try {
161             onExpiration(s);
162             notifyExpiration(s);
163         } finally {
164             afterExpired(s);
165         }
166     }
167 
168     protected void onExpiration(Session session) {
169         onChange(session);
170     }
171 
172     protected void afterExpired(Session session) {
173     }
174 
175     protected void onInvalidation(Session s, InvalidSessionException ise, SessionKey key) {
176         ExpiredSessionException ee = cast(ExpiredSessionException) ise;
177         if (ee !is null) {
178             onExpiration(s, ee, key);
179             return;
180         }
181         tracef("Session with id [%s] is invalid.", s.getId());
182         try {
183             onStop(s);
184             notifyStop(s);
185         } finally {
186             afterStopped(s);
187         }
188     }
189 
190     protected void doValidate(Session session){
191         ValidatingSession vs = cast(ValidatingSession) session;
192         if (vs !is null) {
193             vs.validate();
194         } else {
195             string msg = "The " ~ typeid(this).name ~ " implementation only supports validating " ~
196                     "Session implementations of the " ~ typeid(ValidatingSession).toString() ~ " interface.  " ~
197                     "Please either implement this interface in your session implementation or override the " ~
198                     typeid(AbstractValidatingSessionManager).name ~ 
199                     ".doValidate(Session) method to perform validation.";
200             throw new IllegalStateException(msg);
201         }
202     }
203 
204     /**
205      * Subclass template hook in case per-session timeout is not based on
206      * {@link hunt.shiro.session.Session#getTimeout()}.
207      * <p/>
208      * <p>This implementation merely returns {@link hunt.shiro.session.Session#getTimeout()}</p>
209      *
210      * @param session the session for which to determine session timeout.
211      * @return the time in milliseconds the specified session may remain idle before expiring.
212      */
213     protected long getTimeout(Session session) {
214         return session.getTimeout();
215     }
216 
217     protected SessionValidationScheduler createSessionValidationScheduler() {
218         ExecutorServiceSessionValidationScheduler scheduler;
219 
220         version(HUNT_SHIRO_DEBUG) {
221             warningf("No sessionValidationScheduler set.  Attempting to create default instance.");
222         }
223         scheduler = new ExecutorServiceSessionValidationScheduler(this);
224         scheduler.setInterval(getSessionValidationInterval());
225         version(HUNT_SHIRO_DEBUG) {
226             warningf("Created default SessionValidationScheduler instance of type [" ~ typeid(scheduler).name ~ "].");
227         }
228         return scheduler;
229     }
230 
231     protected void enableSessionValidation() {
232         SessionValidationScheduler scheduler = getSessionValidationScheduler();
233         if (scheduler  is null) {
234             scheduler = createSessionValidationScheduler();
235             setSessionValidationScheduler(scheduler);
236         }
237         // it is possible that that a scheduler was already created and set via 'setSessionValidationScheduler()'
238         // but would not have been enabled/started yet
239         if (!scheduler.isEnabled()) {
240             version(HUNT_SHIRO_DEBUG) {
241                 info("Enabling session validation scheduler...");
242             }
243             scheduler.enableSessionValidation();
244             afterSessionValidationEnabled();
245         }
246     }
247 
248     protected void afterSessionValidationEnabled() {
249     }
250 
251     protected void disableSessionValidation() {
252         beforeSessionValidationDisabled();
253         SessionValidationScheduler scheduler = getSessionValidationScheduler();
254         if (scheduler !is null) {
255             try {
256                 scheduler.disableSessionValidation();
257                 version(HUNT_DEBUG) {
258                     info("Disabled session validation scheduler.");
259                 }
260             } catch (Exception e) {
261                 version(HUNT_DEBUG) {
262                     string msg = "Unable to disable SessionValidationScheduler.  Ignoring (shutting down)...";
263                     tracef(msg, e);
264                 }
265             }
266             LifecycleUtils.destroy(cast(Object)scheduler);
267             setSessionValidationScheduler(null);
268         }
269     }
270 
271     protected void beforeSessionValidationDisabled() {
272     }
273 
274      void destroy() {
275         disableSessionValidation();
276     }
277 
278     /**
279      * @see ValidatingSessionManager#validateSessions()
280      */
281      void validateSessions() {
282         version(HUNT_DEBUG) {
283             info("Validating all active sessions...");
284         }
285 
286         int invalidCount = 0;
287 
288         Session[] activeSessions = getActiveSessions();
289 
290         foreach(Session s ; activeSessions) {
291             try {
292                 //simulate a lookup key to satisfy the method signature.
293                 //this could probably stand to be cleaned up in future versions:
294                 SessionKey key = new DefaultSessionKey(s.getId());
295                 validate(s, key);
296             } catch (InvalidSessionException e) {
297                 version(HUNT_DEBUG) {
298                     ExpiredSessionException ee = cast(ExpiredSessionException)e;
299                     bool expired = ee !is null;
300                     string msg = "Invalidated session with id [" ~ s.getId() ~ "]" ~
301                             (expired ? " (expired)" : " (stopped)");
302                     tracef(msg);
303                 }
304                 invalidCount++;
305             }
306         }
307 
308         version(HUNT_DEBUG) {
309             string msg = "Finished session validation.";
310             if (invalidCount > 0) {
311                 msg ~= "  [" ~ invalidCount.to!string() ~ "] sessions were stopped.";
312             } else {
313                 msg ~= "  No sessions were stopped.";
314             }
315             info(msg);
316         }
317     }
318 
319     protected abstract Session[] getActiveSessions();
320 }