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