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