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.AbstractNativeSessionManager;
20 
21 import hunt.shiro.session.mgt.AbstractSessionManager;
22 import hunt.shiro.session.mgt.DefaultSessionKey;
23 import hunt.shiro.session.mgt.DelegatingSession;
24 import hunt.shiro.session.mgt.ImmutableProxiedSession;
25 import hunt.shiro.session.mgt.NativeSessionManager;
26 import hunt.shiro.session.mgt.SessionContext;
27 import hunt.shiro.session.mgt.SessionKey;
28 
29 import hunt.shiro.Exceptions;
30 import hunt.shiro.event.EventBus;
31 import hunt.shiro.event.EventBusAware;
32 import hunt.shiro.session.Session;
33 
34 import hunt.shiro.session.SessionListener;
35 import hunt.shiro.util.CollectionUtils;
36 
37 import hunt.Exceptions;
38 import hunt.collection;
39 import hunt.logging.Logger;
40 
41 /**
42  * Abstract implementation supporting the {@link NativeSessionManager NativeSessionManager} interface, supporting
43  * {@link SessionListener SessionListener}s and application of the
44  * {@link #getGlobalSessionTimeout() globalSessionTimeout}.
45  *
46  */
47 abstract class AbstractNativeSessionManager : 
48     AbstractSessionManager, NativeSessionManager, EventBusAware {
49 
50     private EventBus eventBus;
51 
52     private Collection!(SessionListener) listeners;
53 
54     this() {
55         this.listeners = new ArrayList!(SessionListener)();
56     }
57 
58      void setSessionListeners(Collection!(SessionListener) listeners) {
59         this.listeners = listeners !is null ? listeners : new ArrayList!(SessionListener)();
60     }
61 
62     //@SuppressWarnings({"UnusedDeclaration"})
63      Collection!(SessionListener) getSessionListeners() {
64         return this.listeners;
65     }
66 
67     /**
68      * Returns the EventBus used to publish SessionEvents.
69      *
70      * @return the EventBus used to publish SessionEvents.
71      */
72     protected EventBus getEventBus() {
73         return eventBus;
74     }
75 
76     /**
77      * Sets the EventBus to use to publish SessionEvents.
78      *
79      * @param eventBus the EventBus to use to publish SessionEvents.
80      */
81     void setEventBus(EventBus eventBus) {
82         this.eventBus = eventBus;
83     }
84 
85     /**
86      * Publishes events on the event bus if the event bus is non-null, otherwise does nothing.
87      *
88      * @param event the event to publish on the event bus if the event bus exists.
89      */
90     protected void publishEvent(Object event) {
91         if (this.eventBus !is null) {
92             this.eventBus.publish(event);
93         }
94     }
95 
96     Session start(SessionContext context) {
97         Session session = createSession(context);
98         applyGlobalSessionTimeout(session);
99         onStart(session, context);
100         notifyStart(session);
101         //Don't expose the EIS-tier Session object to the client-tier:
102         return createExposedSession(session, context);
103     }
104 
105     /**
106      * Creates a new {@code Session Session} instance based on the specified (possibly {@code null})
107      * initialization data.  Implementing classes must manage the persistent state of the returned session such that it
108      * could later be acquired via the {@link #getSession(SessionKey)} method.
109      *
110      * @param context the initialization data that can be used by the implementation or underlying
111      *                {@link SessionFactory} when instantiating the internal {@code Session} instance.
112      * @return the new {@code Session} instance.
113      * @throws hunt.shiro.authz.HostUnauthorizedException
114      *                                if the system access control policy restricts access based
115      *                                on client location/IP and the specified hostAddress hasn't been enabled.
116      * @throws AuthorizationException if the system access control policy does not allow the currently executing
117      *                                caller to start sessions.
118      */
119     protected abstract Session createSession(SessionContext context);
120 
121     protected void applyGlobalSessionTimeout(Session session) {
122         session.setTimeout(getGlobalSessionTimeout());
123         onChange(session);
124     }
125 
126     /**
127      * Template method that allows subclasses to react to a new session being created.
128      * <p/>
129      * This method is invoked <em>before</em> any session listeners are notified.
130      *
131      * @param session the session that was just {@link #createSession created}.
132      * @param context the {@link SessionContext SessionContext} that was used to start the session.
133      */
134     protected void onStart(Session session, SessionContext context) {
135     }
136 
137      Session getSession(SessionKey key){
138         Session session = lookupSession(key);
139         return session !is null ? createExposedSession(session, key) : null;
140     }
141 
142     private Session lookupSession(SessionKey key){
143         if (key is null) {
144             throw new NullPointerException("SessionKey argument cannot be null.");
145         }
146         return doGetSession(key);
147     }
148 
149     private Session lookupRequiredSession(SessionKey key){
150         Session session = lookupSession(key);
151         if (session is null) {
152             string msg = "Unable to locate required Session instance based on SessionKey [" ~ 
153                 (cast(Object)key).toString() ~ "].";
154             throw new UnknownSessionException(msg);
155         }
156         return session;
157     }
158 
159     protected abstract Session doGetSession(SessionKey key);
160 
161     protected Session createExposedSession(Session session, SessionContext context) {
162         return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
163     }
164 
165     protected Session createExposedSession(Session session, SessionKey key) {
166         return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
167     }
168 
169     /**
170      * Returns the session instance to use to pass to registered {@code SessionListener}s for notification
171      * that the session has been invalidated (stopped or expired).
172      * <p/>
173      * The default implementation returns an {@link ImmutableProxiedSession ImmutableProxiedSession} instance to ensure
174      * that the specified {@code session} argument is not modified by any listeners.
175      *
176      * @param session the {@code Session} object being invalidated.
177      * @return the {@code Session} instance to use to pass to registered {@code SessionListener}s for notification.
178      */
179     protected Session beforeInvalidNotification(Session session) {
180         return new ImmutableProxiedSession(session);
181     }
182 
183     /**
184      * Notifies any interested {@link SessionListener}s that a Session has started.  This method is invoked
185      * <em>after</em> the {@link #onStart onStart} method is called.
186      *
187      * @param session the session that has just started that will be delivered to any
188      *                {@link #setSessionListeners(java.util.Collection) registered} session listeners.
189      * @see SessionListener#onStart(hunt.shiro.session.Session)
190      */
191     protected void notifyStart(Session session) {
192         foreach(SessionListener listener ; this.listeners) {
193             listener.onStart(session);
194         }
195     }
196 
197     protected void notifyStop(Session session) {
198         Session forNotification = beforeInvalidNotification(session);
199         foreach(SessionListener listener ; this.listeners) {
200             listener.onStop(forNotification);
201         }
202     }
203 
204     protected void notifyExpiration(Session session) {
205         Session forNotification = beforeInvalidNotification(session);
206         foreach(SessionListener listener ; this.listeners) {
207             listener.onExpiration(forNotification);
208         }
209     }
210 
211     Date getStartTimestamp(SessionKey key) {
212         return lookupRequiredSession(key).getStartTimestamp();
213     }
214 
215     Date getLastAccessTime(SessionKey key) {
216         return lookupRequiredSession(key).getLastAccessTime();
217     }
218 
219      long getTimeout(SessionKey key){
220         return lookupRequiredSession(key).getTimeout();
221     }
222 
223      void setTimeout(SessionKey key, long maxIdleTimeInMillis){
224         Session s = lookupRequiredSession(key);
225         s.setTimeout(maxIdleTimeInMillis);
226         onChange(s);
227     }
228 
229      void touch(SessionKey key){
230         Session s = lookupRequiredSession(key);
231         s.touch();
232         onChange(s);
233     }
234 
235      string getHost(SessionKey key) {
236         return lookupRequiredSession(key).getHost();
237     }
238 
239     Object[] getAttributeKeys(SessionKey key) {
240         Object[] c = lookupRequiredSession(key).getAttributeKeys();
241         return c;
242     }
243 
244     Object getAttribute(SessionKey sessionKey, Object attributeKey){
245         return lookupRequiredSession(sessionKey).getAttribute(attributeKey);
246     }
247 
248      void setAttribute(SessionKey sessionKey, Object attributeKey, Object value){
249         if (value  is null) {
250             removeAttribute(sessionKey, attributeKey);
251         } else {
252             Session s = lookupRequiredSession(sessionKey);
253             s.setAttribute(attributeKey, value);
254             onChange(s);
255         }
256     }
257 
258      Object removeAttribute(SessionKey sessionKey, Object attributeKey){
259         Session s = lookupRequiredSession(sessionKey);
260         Object removed = s.removeAttribute(attributeKey);
261         if (removed !is null) {
262             onChange(s);
263         }
264         return removed;
265     }
266 
267      bool isValid(SessionKey key) {
268         try {
269             checkValid(key);
270             return true;
271         } catch (InvalidSessionException e) {
272             return false;
273         }
274     }
275 
276      void stop(SessionKey key){
277         Session session = lookupRequiredSession(key);
278         try {
279             version(HUNT_SHIRO_DEBUG) {
280                 tracef("Stopping session with id [" ~ session.getId() ~ "]");
281             }
282             session.stop();
283             onStop(session, key);
284             notifyStop(session);
285         } finally {
286             afterStopped(session);
287         }
288     }
289 
290     protected void onStop(Session session, SessionKey key) {
291         onStop(session);
292     }
293 
294     protected void onStop(Session session) {
295         onChange(session);
296     }
297 
298     protected void afterStopped(Session session) {
299     }
300 
301      void checkValid(SessionKey key){
302         //just try to acquire it.  If there is a problem, an exception will be thrown:
303         lookupRequiredSession(key);
304     }
305 
306     protected void onChange(Session s) {
307     }
308 }