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 }