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.DefaultSessionManager;
20 
21 import hunt.shiro.session.mgt.AbstractValidatingSessionManager;
22 import hunt.shiro.session.mgt.SessionContext;
23 import hunt.shiro.session.mgt.SessionFactory;
24 import hunt.shiro.session.mgt.SessionKey;
25 import hunt.shiro.session.mgt.SimpleSession;
26 import hunt.shiro.session.mgt.SimpleSessionFactory;
27 
28 
29 
30 import hunt.shiro.cache.CacheManager;
31 import hunt.shiro.cache.CacheManagerAware;
32 import hunt.shiro.session.Session;
33 import hunt.shiro.Exceptions;
34 import hunt.shiro.session.mgt.eis.MemorySessionDAO;
35 import hunt.shiro.session.mgt.eis.SessionDAO;
36 import hunt.logging.Logger;
37 
38 import hunt.util.Common;
39 import hunt.collection;
40 import hunt.collection.Collections;
41 
42 import std.array;
43 // import java.util.Date;
44 
45 /**
46  * Default business-tier implementation of a {@link ValidatingSessionManager}.  All session CRUD operations are
47  * delegated to an internal {@link SessionDAO}.
48  *
49  */
50 class DefaultSessionManager : AbstractValidatingSessionManager, CacheManagerAware {
51 
52     private SessionFactory sessionFactory;
53 
54     protected SessionDAO sessionDAO;  //todo - move SessionDAO up to AbstractValidatingSessionManager?
55 
56     private CacheManager cacheManager;
57 
58     private bool deleteInvalidSessions;
59 
60     this() {
61         this.deleteInvalidSessions = true;
62         this.sessionFactory = new SimpleSessionFactory();
63         this.sessionDAO = new MemorySessionDAO();
64     }
65 
66      void setSessionDAO(SessionDAO sessionDAO) {
67         this.sessionDAO = sessionDAO;
68         applyCacheManagerToSessionDAO();
69     }
70 
71      SessionDAO getSessionDAO() {
72         return this.sessionDAO;
73     }
74 
75     /**
76      * Returns the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
77      * is a {@link SimpleSessionFactory}.
78      *
79      * @return the {@code SessionFactory} used to generate new {@link Session} instances.
80      */
81      SessionFactory getSessionFactory() {
82         return sessionFactory;
83     }
84 
85     /**
86      * Sets the {@code SessionFactory} used to generate new {@link Session} instances.  The default instance
87      * is a {@link SimpleSessionFactory}.
88      *
89      * @param sessionFactory the {@code SessionFactory} used to generate new {@link Session} instances.
90      */
91      void setSessionFactory(SessionFactory sessionFactory) {
92         this.sessionFactory = sessionFactory;
93     }
94 
95     /**
96      * Returns {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
97      * {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.  The
98      * default is {@code true} to ensure no orphans exist in the underlying data store.
99      * <h4>Usage</h4>
100      * It is ok to set this to {@code false} <b><em>ONLY</em></b> if you have some other process that you manage yourself
101      * that periodically deletes invalid sessions from the backing data store over time, such as via a Quartz or Cron
102      * job.  If you do not do this, the invalid sessions will become 'orphans' and fill up the data store over time.
103      * <p/>
104      * This property is provided because some systems need the ability to perform querying/reporting against sessions in
105      * the data store, even after they have stopped or expired.  Setting this attribute to {@code false} will allow
106      * such querying, but with the caveat that the application developer/configurer deletes the sessions themselves by
107      * some other means (cron, quartz, etc).
108      *
109      * @return {@code true} if sessions should be automatically deleted after they are discovered to be invalid,
110      *         {@code false} if invalid sessions will be manually deleted by some process external to Shiro's control.
111      */
112      bool isDeleteInvalidSessions() {
113         return deleteInvalidSessions;
114     }
115 
116     /**
117      * Sets whether or not sessions should be automatically deleted after they are discovered to be invalid.  Default
118      * value is {@code true} to ensure no orphans will exist in the underlying data store.
119      * <h4>WARNING</h4>
120      * Only set this value to {@code false} if you are manually going to remove sessions yourself by some process
121      * (quartz, cron, etc) external to Shiro's control.  See the
122      * {@link #isDeleteInvalidSessions() isDeleteInvalidSessions()} JavaDoc for more.
123      *
124      * @param deleteInvalidSessions whether or not sessions should be automatically deleted after they are discovered
125      *                              to be invalid.
126      */
127     //@SuppressWarnings({"UnusedDeclaration"})
128      void setDeleteInvalidSessions(bool deleteInvalidSessions) {
129         this.deleteInvalidSessions = deleteInvalidSessions;
130     }
131 
132      void setCacheManager(CacheManager cacheManager) {
133         this.cacheManager = cacheManager;
134         applyCacheManagerToSessionDAO();
135     }
136 
137     /**
138      * Sets the internal {@code CacheManager} on the {@code SessionDAO} if it implements the
139      * {@link hunt.shiro.cache.CacheManagerAware CacheManagerAware} interface.
140      * <p/>
141      * This method is called after setting a cacheManager via the
142      * {@link #setCacheManager(hunt.shiro.cache.CacheManager) setCacheManager} method <em>em</em> when
143      * setting a {@code SessionDAO} via the {@link #setSessionDAO} method to allow it to be propagated
144      * in either case.
145      *
146      */
147     private void applyCacheManagerToSessionDAO() {
148         CacheManagerAware cma = cast(CacheManagerAware) this.sessionDAO;
149         if (this.cacheManager !is null && this.sessionDAO !is null && cma !is null) {
150             cma.setCacheManager(this.cacheManager);
151         }
152     }
153 
154     override protected Session doCreateSession(SessionContext context) {
155         Session s = newSessionInstance(context);
156         version(HUNT_SHIRO_DEBUG) {
157             tracef("Creating session for host %s", s.getHost());
158         }
159         create(s);
160         return s;
161     }
162 
163     protected Session newSessionInstance(SessionContext context) {
164         return getSessionFactory().createSession(context);
165     }
166 
167     /**
168      * Persists the given session instance to an underlying EIS (Enterprise Information System).  This implementation
169      * delegates and calls
170      * <code>this.{@link SessionDAO sessionDAO}.{@link SessionDAO#create(hunt.shiro.session.Session) create}(session);<code>
171      *
172      * @param session the Session instance to persist to the underlying EIS.
173      */
174     protected void create(Session session) {
175         version(HUNT_SHIRO_DEBUG) {
176             tracef("Creating new EIS record for new session instance [" ~ (cast(Object)session).toString() ~ "]");
177         }
178         sessionDAO.create(session);
179     }
180 
181     override
182     protected void onStop(Session session) {
183         SimpleSession ss = cast(SimpleSession) session;
184         if (ss !is null) {
185             Date stopTs = ss.getStopTimestamp().value;
186             ss.setLastAccessTime(stopTs);
187         }
188         onChange(session);
189     }
190 
191     override
192     protected void afterStopped(Session session) {
193         if (isDeleteInvalidSessions()) {
194             remove(session);
195         }
196     }
197 
198     override protected void onExpiration(Session session) {
199         SimpleSession ss = cast(SimpleSession) session;
200         if (ss !is null) {
201             ss.setExpired(true);
202         }
203         onChange(session);
204     }
205 
206     override
207     protected void afterExpired(Session session) {
208         if (isDeleteInvalidSessions()) {
209             remove(session);
210         }
211     }
212 
213     override protected void onChange(Session session) {
214         sessionDAO.update(session);
215     }
216 
217     override protected Session retrieveSession(SessionKey sessionKey) {
218         string sessionId = getSessionId(sessionKey);
219         if (sessionId.empty()) {
220             warningf("Unable to resolve session ID from SessionKey [%s].  Returning null to indicate a " ~
221                     "session could not be found.", sessionKey);
222             return null;
223         }
224         Session s = retrieveSessionFromDataSource(sessionId);
225         if (s is null) {
226             //session ID was provided, meaning one is expected to be found, but we couldn't find one:
227             string msg = "Could not find session with ID [" ~ sessionId ~ "]";
228             throw new UnknownSessionException(msg);
229         }
230         return s;
231     }
232 
233     protected string getSessionId(SessionKey sessionKey) {
234         return sessionKey.getSessionId();
235     }
236 
237     protected Session retrieveSessionFromDataSource(string sessionId){
238         return sessionDAO.readSession(sessionId);
239     }
240 
241     protected void remove(Session session) {
242         sessionDAO.remove(session);
243     }
244 
245     override protected Session[] getActiveSessions() {
246         Session[] active = sessionDAO.getActiveSessions();
247         return active;
248     }
249 
250 }