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