001 /*
002 * Copyright 2008 Les Hazlewood
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.apache.shiro.session.mgt;
017
018 import org.apache.shiro.authz.AuthorizationException;
019 import org.apache.shiro.session.*;
020 import org.apache.shiro.util.CollectionUtils;
021 import org.slf4j.Logger;
022 import org.slf4j.LoggerFactory;
023
024 import java.util.ArrayList;
025 import java.util.Collection;
026 import java.util.Collections;
027 import java.util.Date;
028
029 /**
030 * Abstract implementation supporting the {@link NativeSessionManager NativeSessionManager} interface, supporting
031 * {@link SessionListener SessionListener}s and application of the
032 * {@link #getGlobalSessionTimeout() globalSessionTimeout}.
033 *
034 * @author Les Hazlewood
035 * @since 1.0
036 */
037 public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager {
038
039 private static final Logger log = LoggerFactory.getLogger(AbstractSessionManager.class);
040
041 private Collection<SessionListener> listeners;
042
043 public AbstractNativeSessionManager() {
044 this.listeners = new ArrayList<SessionListener>();
045 }
046
047 public void setSessionListeners(Collection<SessionListener> listeners) {
048 this.listeners = listeners != null ? listeners : new ArrayList<SessionListener>();
049 }
050
051 @SuppressWarnings({"UnusedDeclaration"})
052 public Collection<SessionListener> getSessionListeners() {
053 return this.listeners;
054 }
055
056 public Session start(SessionContext context) {
057 Session session = createSession(context);
058 applyGlobalSessionTimeout(session);
059 onStart(session, context);
060 notifyStart(session);
061 //Don't expose the EIS-tier Session object to the client-tier:
062 return createExposedSession(session, context);
063 }
064
065 /**
066 * Creates a new {@code Session Session} instance based on the specified (possibly {@code null})
067 * initialization data. Implementing classes must manage the persistent state of the returned session such that it
068 * could later be acquired via the {@link #getSession(SessionKey)} method.
069 *
070 * @param context the initialization data that can be used by the implementation or underlying
071 * {@link SessionFactory} when instantiating the internal {@code Session} instance.
072 * @return the new {@code Session} instance.
073 * @throws org.apache.shiro.authz.HostUnauthorizedException
074 * if the system access control policy restricts access based
075 * on client location/IP and the specified hostAddress hasn't been enabled.
076 * @throws AuthorizationException if the system access control policy does not allow the currently executing
077 * caller to start sessions.
078 */
079 protected abstract Session createSession(SessionContext context) throws AuthorizationException;
080
081 protected void applyGlobalSessionTimeout(Session session) {
082 session.setTimeout(getGlobalSessionTimeout());
083 onChange(session);
084 }
085
086 /**
087 * Template method that allows subclasses to react to a new session being created.
088 * <p/>
089 * This method is invoked <em>before</em> any session listeners are notified.
090 *
091 * @param session the session that was just {@link #createSession created}.
092 * @param context the {@link SessionContext SessionContext} that was used to start the session.
093 */
094 protected void onStart(Session session, SessionContext context) {
095 }
096
097 public Session getSession(SessionKey key) throws SessionException {
098 Session session = lookupSession(key);
099 return session != null ? createExposedSession(session, key) : null;
100 }
101
102 private Session lookupSession(SessionKey key) throws SessionException {
103 if (key == null) {
104 throw new NullPointerException("SessionKey argument cannot be null.");
105 }
106 return doGetSession(key);
107 }
108
109 private Session lookupRequiredSession(SessionKey key) throws SessionException {
110 Session session = lookupSession(key);
111 if (session == null) {
112 String msg = "Unable to locate required Session instance based on SessionKey [" + key + "].";
113 throw new UnknownSessionException(msg);
114 }
115 return session;
116 }
117
118 protected abstract Session doGetSession(SessionKey key) throws InvalidSessionException;
119
120 protected Session createExposedSession(Session session, SessionContext context) {
121 return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
122 }
123
124 protected Session createExposedSession(Session session, SessionKey key) {
125 return new DelegatingSession(this, new DefaultSessionKey(session.getId()));
126 }
127
128 /**
129 * Returns the session instance to use to pass to registered {@code SessionListener}s for notification
130 * that the session has been invalidated (stopped or expired).
131 * <p/>
132 * The default implementation returns an {@link ImmutableProxiedSession ImmutableProxiedSession} instance to ensure
133 * that the specified {@code session} argument is not modified by any listeners.
134 *
135 * @param session the {@code Session} object being invalidated.
136 * @return the {@code Session} instance to use to pass to registered {@code SessionListener}s for notification.
137 */
138 protected Session beforeInvalidNotification(Session session) {
139 return new ImmutableProxiedSession(session);
140 }
141
142 /**
143 * Notifies any interested {@link SessionListener}s that a Session has started. This method is invoked
144 * <em>after</em> the {@link #onStart onStart} method is called.
145 *
146 * @param session the session that has just started that will be delivered to any
147 * {@link #setSessionListeners(java.util.Collection) registered} session listeners.
148 * @see SessionListener#onStart(org.apache.shiro.session.Session)
149 */
150 protected void notifyStart(Session session) {
151 for (SessionListener listener : this.listeners) {
152 listener.onStart(session);
153 }
154 }
155
156 protected void notifyStop(Session session) {
157 Session forNotification = beforeInvalidNotification(session);
158 for (SessionListener listener : this.listeners) {
159 listener.onStop(forNotification);
160 }
161 }
162
163 protected void notifyExpiration(Session session) {
164 Session forNotification = beforeInvalidNotification(session);
165 for (SessionListener listener : this.listeners) {
166 listener.onExpiration(forNotification);
167 }
168 }
169
170 public Date getStartTimestamp(SessionKey key) {
171 return lookupRequiredSession(key).getStartTimestamp();
172 }
173
174 public Date getLastAccessTime(SessionKey key) {
175 return lookupRequiredSession(key).getLastAccessTime();
176 }
177
178 public long getTimeout(SessionKey key) throws InvalidSessionException {
179 return lookupRequiredSession(key).getTimeout();
180 }
181
182 public void setTimeout(SessionKey key, long maxIdleTimeInMillis) throws InvalidSessionException {
183 Session s = lookupRequiredSession(key);
184 s.setTimeout(maxIdleTimeInMillis);
185 onChange(s);
186 }
187
188 public void touch(SessionKey key) throws InvalidSessionException {
189 Session s = lookupRequiredSession(key);
190 s.touch();
191 onChange(s);
192 }
193
194 public String getHost(SessionKey key) {
195 return lookupRequiredSession(key).getHost();
196 }
197
198 public Collection<Object> getAttributeKeys(SessionKey key) {
199 Collection<Object> c = lookupRequiredSession(key).getAttributeKeys();
200 if (!CollectionUtils.isEmpty(c)) {
201 return Collections.unmodifiableCollection(c);
202 }
203 return Collections.emptySet();
204 }
205
206 public Object getAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
207 return lookupRequiredSession(sessionKey).getAttribute(attributeKey);
208 }
209
210 public void setAttribute(SessionKey sessionKey, Object attributeKey, Object value) throws InvalidSessionException {
211 if (value == null) {
212 removeAttribute(sessionKey, attributeKey);
213 } else {
214 Session s = lookupRequiredSession(sessionKey);
215 s.setAttribute(attributeKey, value);
216 onChange(s);
217 }
218 }
219
220 public Object removeAttribute(SessionKey sessionKey, Object attributeKey) throws InvalidSessionException {
221 Session s = lookupRequiredSession(sessionKey);
222 Object removed = s.removeAttribute(attributeKey);
223 if (removed != null) {
224 onChange(s);
225 }
226 return removed;
227 }
228
229 public boolean isValid(SessionKey key) {
230 try {
231 checkValid(key);
232 return true;
233 } catch (InvalidSessionException e) {
234 return false;
235 }
236 }
237
238 public void stop(SessionKey key) throws InvalidSessionException {
239 Session session = lookupRequiredSession(key);
240 if (log.isDebugEnabled()) {
241 log.debug("Stopping session with id [" + session.getId() + "]");
242 }
243 session.stop();
244 onStop(session, key);
245 notifyStop(session);
246 afterStopped(session);
247 }
248
249 protected void onStop(Session session, SessionKey key) {
250 onStop(session);
251 }
252
253 protected void onStop(Session session) {
254 onChange(session);
255 }
256
257 protected void afterStopped(Session session) {
258 }
259
260 public void checkValid(SessionKey key) throws InvalidSessionException {
261 //just try to acquire it. If there is a problem, an exception will be thrown:
262 lookupRequiredSession(key);
263 }
264
265 protected void onChange(Session s) {
266 }
267 }