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}