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}