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