1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 module hunt.shiro.session.mgt.eis.AbstractSessionDAO;
20 
21 import hunt.shiro.session.mgt.eis.SessionDAO;
22 import hunt.shiro.session.mgt.eis.SessionIdGenerator;
23 
24 import hunt.shiro.session.Session;
25 import hunt.shiro.Exceptions;
26 import hunt.shiro.session.mgt.SimpleSession;
27 
28 import hunt.Exceptions;
29 import hunt.util.Common;
30 
31 
32 /**
33  * An abstract {@code SessionDAO} implementation that performs some sanity checks on session creation and reading and
34  * allows for pluggable Session ID generation strategies if desired.  The {@code SessionDAO}
35  * {@link SessionDAO#update update} and {@link SessionDAO#remove remove} methods are left to
36  * subclasses.
37  * <h3>Session ID Generation</h3>
38  * This class also allows for plugging in a {@link SessionIdGenerator} for custom ID generation strategies.  This is
39  * optional, as the default generator is probably sufficient for most cases.  Subclass implementations that do use a
40  * generator (default or custom) will want to call the
41  * {@link #generateSessionId(hunt.shiro.session.Session)} method from within their {@link #doCreate}
42  * implementations.
43  * <p/>
44  * Subclass implementations that rely on the EIS data store to generate the ID automatically (e.g. when the session
45  * ID is also an auto-generated primary key), they can simply ignore the {@code SessionIdGenerator} concept
46  * entirely and just return the data store's ID from the {@link #doCreate} implementation.
47  *
48  */
49 abstract class AbstractSessionDAO : SessionDAO {
50 
51     /**
52      * Optional SessionIdGenerator instance available to subclasses via the
53      * {@link #generateSessionId(hunt.shiro.session.Session)} method.
54      */
55     private SessionIdGenerator sessionIdGenerator;
56 
57     /**
58      * Default no-arg constructor that defaults the {@link #setSessionIdGenerator sessionIdGenerator} to be a
59      * {@link hunt.shiro.session.mgt.eis.UuidSessionIdGenerator}.
60      */
61     this() {
62         this.sessionIdGenerator = new UuidSessionIdGenerator();
63     }
64 
65     /**
66      * Returns the {@code SessionIdGenerator} used by the {@link #generateSessionId(hunt.shiro.session.Session)}
67      * method.  Unless overridden by the {@link #setSessionIdGenerator(SessionIdGenerator)} method, the default instance
68      * is a {@link JavaUuidSessionIdGenerator}.
69      *
70      * @return the {@code SessionIdGenerator} used by the {@link #generateSessionId(hunt.shiro.session.Session)}
71      *         method.
72      */
73      SessionIdGenerator getSessionIdGenerator() {
74         return sessionIdGenerator;
75     }
76 
77     /**
78      * Sets the {@code SessionIdGenerator} used by the {@link #generateSessionId(hunt.shiro.session.Session)}
79      * method.  Unless overridden by this method, the default instance ss a {@link JavaUuidSessionIdGenerator}.
80      *
81      * @param sessionIdGenerator the {@code SessionIdGenerator} to use in the
82      *                           {@link #generateSessionId(hunt.shiro.session.Session)} method.
83      */
84      void setSessionIdGenerator(SessionIdGenerator sessionIdGenerator) {
85         this.sessionIdGenerator = sessionIdGenerator;
86     }
87 
88     /**
89      * Generates a new ID to be applied to the specified {@code session} instance.  This method is usually called
90      * from within a subclass's {@link #doCreate} implementation where they assign the returned id to the session
91      * instance and then create a record with this ID in the EIS data store.
92      * <p/>
93      * Subclass implementations backed by EIS data stores that auto-generate IDs during record creation, such as
94      * relational databases, don't need to use this method or the {@link #getSessionIdGenerator() sessionIdGenerator}
95      * attribute - they can simply return the data store's generated ID from the {@link #doCreate} implementation
96      * if desired.
97      * <p/>
98      * This implementation uses the {@link #setSessionIdGenerator configured} {@link SessionIdGenerator} to create
99      * the ID.
100      *
101      * @param session the new session instance for which an ID will be generated and then assigned
102      * @return the generated ID to assign
103      */
104     protected string generateSessionId(Session session) {
105         if (this.sessionIdGenerator is null) {
106             string msg = "sessionIdGenerator attribute has not been configured.";
107             throw new IllegalStateException(msg);
108         }
109         return this.sessionIdGenerator.generateId(session);
110     }
111 
112     /**
113      * Creates the session by delegating EIS creation to subclasses via the {@link #doCreate} method, and then
114      * asserting that the returned sessionId is not null.
115      *
116      * @param session Session object to create in the EIS and associate with an ID.
117      */
118      string create(Session session) {
119         string sessionId = doCreate(session);
120         verifySessionId(sessionId);
121         return sessionId;
122     }
123 
124     /**
125      * Ensures the sessionId returned from the subclass implementation of {@link #doCreate} is not null and not
126      * already in use.
127      *
128      * @param sessionId session id returned from the subclass implementation of {@link #doCreate}
129      */
130     private void verifySessionId(string sessionId) {
131         if (sessionId  is null) {
132             string msg = "sessionId returned from doCreate implementation is null.  Please verify the implementation.";
133             throw new IllegalStateException(msg);
134         }
135     }
136 
137     /**
138      * Utility method available to subclasses that wish to
139      * assign a generated session ID to the session instance directly.  This method is not used by the
140      * {@code AbstractSessionDAO} implementation directly, but it is provided so subclasses don't
141      * need to know the {@code Session} implementation if they don't need to.
142      * <p/>
143      * This default implementation casts the argument to a {@link SimpleSession}, Shiro's default EIS implementation.
144      *
145      * @param session   the session instance to which the sessionId will be applied
146      * @param sessionId the id to assign to the specified session instance.
147      */
148     protected void assignSessionId(Session session, string sessionId) {
149         (cast(SimpleSession) session).setId(sessionId);
150     }
151 
152     /**
153      * Subclass hook to actually persist the given <tt>Session</tt> instance to the underlying EIS.
154      *
155      * @param session the Session instance to persist to the EIS.
156      * @return the id of the session created in the EIS (i.e. this is almost always a primary key and should be the
157      *         value returned from {@link hunt.shiro.session.Session#getId() Session.getId()}.
158      */
159     protected abstract string doCreate(Session session);
160 
161     /**
162      * Retrieves the Session object from the underlying EIS identified by <tt>sessionId</tt> by delegating to
163      * the {@link #doReadSession(java.io.Serializable)} method.  If {@code null} is returned from that method, an
164      * {@link UnknownSessionException} will be thrown.
165      *
166      * @param sessionId the id of the session to retrieve from the EIS.
167      * @return the session identified by <tt>sessionId</tt> in the EIS.
168      * @throws UnknownSessionException if the id specified does not correspond to any session in the EIS.
169      */
170      Session readSession(string sessionId){
171         Session s = doReadSession(sessionId);
172         if (s  is null) {
173             throw new UnknownSessionException("There is no session with id [" ~ 
174                 sessionId ~ "]");
175         }
176         return s;
177     }
178 
179     /**
180      * Subclass implementation hook that retrieves the Session object from the underlying EIS or {@code null} if a
181      * session with that ID could not be found.
182      *
183      * @param sessionId the id of the <tt>Session</tt> to retrieve.
184      * @return the Session in the EIS identified by <tt>sessionId</tt> or {@code null} if a
185      *         session with that ID could not be found.
186      */
187     protected abstract Session doReadSession(string sessionId);
188 
189 }