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.eis;
020
021import org.apache.shiro.session.Session;
022import org.apache.shiro.session.UnknownSessionException;
023import org.apache.shiro.session.mgt.SimpleSession;
024
025import java.io.Serializable;
026
027
028/**
029 * An abstract {@code SessionDAO} implementation that performs some sanity checks on session creation and reading and
030 * allows for pluggable Session ID generation strategies if desired.  The {@code SessionDAO}
031 * {@link SessionDAO#update update} and {@link SessionDAO#delete delete} methods are left to
032 * subclasses.
033 * <h3>Session ID Generation</h3>
034 * This class also allows for plugging in a {@link SessionIdGenerator} for custom ID generation strategies.  This is
035 * optional, as the default generator is probably sufficient for most cases.  Subclass implementations that do use a
036 * generator (default or custom) will want to call the
037 * {@link #generateSessionId(org.apache.shiro.session.Session)} method from within their {@link #doCreate}
038 * implementations.
039 * <p/>
040 * Subclass implementations that rely on the EIS data store to generate the ID automatically (e.g. when the session
041 * ID is also an auto-generated primary key), they can simply ignore the {@code SessionIdGenerator} concept
042 * entirely and just return the data store's ID from the {@link #doCreate} implementation.
043 *
044 * @since 1.0
045 */
046public abstract class AbstractSessionDAO implements SessionDAO {
047
048    /**
049     * Optional SessionIdGenerator instance available to subclasses via the
050     * {@link #generateSessionId(org.apache.shiro.session.Session)} method.
051     */
052    private SessionIdGenerator sessionIdGenerator;
053
054    /**
055     * Default no-arg constructor that defaults the {@link #setSessionIdGenerator sessionIdGenerator} to be a
056     * {@link org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator}.
057     */
058    public AbstractSessionDAO() {
059        this.sessionIdGenerator = new JavaUuidSessionIdGenerator();
060    }
061
062    /**
063     * Returns the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
064     * method.  Unless overridden by the {@link #setSessionIdGenerator(SessionIdGenerator)} method, the default instance
065     * is a {@link JavaUuidSessionIdGenerator}.
066     *
067     * @return the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
068     *         method.
069     */
070    public SessionIdGenerator getSessionIdGenerator() {
071        return sessionIdGenerator;
072    }
073
074    /**
075     * Sets the {@code SessionIdGenerator} used by the {@link #generateSessionId(org.apache.shiro.session.Session)}
076     * method.  Unless overridden by this method, the default instance ss a {@link JavaUuidSessionIdGenerator}.
077     *
078     * @param sessionIdGenerator the {@code SessionIdGenerator} to use in the
079     *                           {@link #generateSessionId(org.apache.shiro.session.Session)} method.
080     */
081    public void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
082        this.sessionIdGenerator = sessionIdGenerator;
083    }
084
085    /**
086     * Generates a new ID to be applied to the specified {@code session} instance.  This method is usually called
087     * from within a subclass's {@link #doCreate} implementation where they assign the returned id to the session
088     * instance and then create a record with this ID in the EIS data store.
089     * <p/>
090     * Subclass implementations backed by EIS data stores that auto-generate IDs during record creation, such as
091     * relational databases, don't need to use this method or the {@link #getSessionIdGenerator() sessionIdGenerator}
092     * attribute - they can simply return the data store's generated ID from the {@link #doCreate} implementation
093     * if desired.
094     * <p/>
095     * This implementation uses the {@link #setSessionIdGenerator configured} {@link SessionIdGenerator} to create
096     * the ID.
097     *
098     * @param session the new session instance for which an ID will be generated and then assigned
099     * @return the generated ID to assign
100     */
101    protected Serializable generateSessionId(Session session) {
102        if (this.sessionIdGenerator == null) {
103            String msg = "sessionIdGenerator attribute has not been configured.";
104            throw new IllegalStateException(msg);
105        }
106        return this.sessionIdGenerator.generateId(session);
107    }
108
109    /**
110     * Creates the session by delegating EIS creation to subclasses via the {@link #doCreate} method, and then
111     * asserting that the returned sessionId is not null.
112     *
113     * @param session Session object to create in the EIS and associate with an ID.
114     */
115    public Serializable create(Session session) {
116        Serializable sessionId = doCreate(session);
117        verifySessionId(sessionId);
118        return sessionId;
119    }
120
121    /**
122     * Ensures the sessionId returned from the subclass implementation of {@link #doCreate} is not null and not
123     * already in use.
124     *
125     * @param sessionId session id returned from the subclass implementation of {@link #doCreate}
126     */
127    private void verifySessionId(Serializable sessionId) {
128        if (sessionId == null) {
129            String msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
130            throw new IllegalStateException(msg);
131        }
132    }
133
134    /**
135     * Utility method available to subclasses that wish to
136     * assign a generated session ID to the session instance directly.  This method is not used by the
137     * {@code AbstractSessionDAO} implementation directly, but it is provided so subclasses don't
138     * need to know the {@code Session} implementation if they don't need to.
139     * <p/>
140     * This default implementation casts the argument to a {@link SimpleSession}, Shiro's default EIS implementation.
141     *
142     * @param session   the session instance to which the sessionId will be applied
143     * @param sessionId the id to assign to the specified session instance.
144     */
145    protected void assignSessionId(Session session, Serializable sessionId) {
146        ((SimpleSession) session).setId(sessionId);
147    }
148
149    /**
150     * Subclass hook to actually persist the given <tt>Session</tt> instance to the underlying EIS.
151     *
152     * @param session the Session instance to persist to the EIS.
153     * @return the id of the session created in the EIS (i.e. this is almost always a primary key and should be the
154     *         value returned from {@link org.apache.shiro.session.Session#getId() Session.getId()}.
155     */
156    protected abstract Serializable doCreate(Session session);
157
158    /**
159     * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt> by delegating to
160     * the {@link #doReadSession(java.io.Serializable)} method.  If {@code null} is returned from that method, an
161     * {@link UnknownSessionException} will be thrown.
162     *
163     * @param sessionId the id of the session to retrieve from the EIS.
164     * @return the session identified by <tt>sessionId</tt> in the EIS.
165     * @throws UnknownSessionException if the id specified does not correspond to any session in the EIS.
166     */
167    public Session readSession(Serializable sessionId) throws UnknownSessionException {
168        Session s = doReadSession(sessionId);
169        if (s == null) {
170            throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
171        }
172        return s;
173    }
174
175    /**
176     * Subclass implementation hook that retrieves the Session object from the underlying EIS or {@code null} if a
177     * session with that ID could not be found.
178     *
179     * @param sessionId the id of the <tt>Session</tt> to retrieve.
180     * @return the Session in the EIS identified by <tt>sessionId</tt> or {@code null} if a
181     *         session with that ID could not be found.
182     */
183    protected abstract Session doReadSession(Serializable sessionId);
184
185}