1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.session.mgt;
20
21 import org.apache.shiro.session.ExpiredSessionException;
22 import org.apache.shiro.session.InvalidSessionException;
23 import org.apache.shiro.session.StoppedSessionException;
24 import org.apache.shiro.util.CollectionUtils;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import java.io.IOException;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.io.Serializable;
32 import java.text.DateFormat;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.Date;
36 import java.util.HashMap;
37 import java.util.Map;
38
39
40
41
42
43
44
45
46 @SuppressWarnings("checkstyle:MethodCount")
47 public class SimpleSession implements ValidatingSession, Serializable {
48
49 protected static final long MILLIS_PER_SECOND = 1000;
50 protected static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
51 protected static final long MILLIS_PER_HOUR = 60 * MILLIS_PER_MINUTE;
52
53
54 static int bitIndexCounter;
55
56
57
58
59
60
61 private static final long serialVersionUID = -7125642695178165650L;
62
63 private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSession.class);
64 private static final int ID_BIT_MASK = 1 << bitIndexCounter++;
65 private static final int START_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
66 private static final int STOP_TIMESTAMP_BIT_MASK = 1 << bitIndexCounter++;
67 private static final int LAST_ACCESS_TIME_BIT_MASK = 1 << bitIndexCounter++;
68 private static final int TIMEOUT_BIT_MASK = 1 << bitIndexCounter++;
69 private static final int EXPIRED_BIT_MASK = 1 << bitIndexCounter++;
70 private static final int HOST_BIT_MASK = 1 << bitIndexCounter++;
71 private static final int ATTRIBUTES_BIT_MASK = 1 << bitIndexCounter++;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 private transient Serializable id;
91 private transient Date startTimestamp;
92 private transient Date stopTimestamp;
93 private transient Date lastAccessTime;
94 private transient long timeout;
95 private transient boolean expired;
96 private transient String host;
97 private transient Map<Object, Object> attributes;
98
99 public SimpleSession() {
100
101 this.timeout = DefaultSessionManager.DEFAULT_GLOBAL_SESSION_TIMEOUT;
102 this.startTimestamp = new Date();
103 this.lastAccessTime = this.startTimestamp;
104 }
105
106 public SimpleSession(String host) {
107 this();
108 this.host = host;
109 }
110
111 public Serializable getId() {
112 return this.id;
113 }
114
115 public void setId(Serializable id) {
116 this.id = id;
117 }
118
119 public Date getStartTimestamp() {
120 return startTimestamp;
121 }
122
123 public void setStartTimestamp(Date startTimestamp) {
124 this.startTimestamp = startTimestamp;
125 }
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 public Date getStopTimestamp() {
145 return stopTimestamp;
146 }
147
148 public void setStopTimestamp(Date stopTimestamp) {
149 this.stopTimestamp = stopTimestamp;
150 }
151
152 public Date getLastAccessTime() {
153 return lastAccessTime;
154 }
155
156 public void setLastAccessTime(Date lastAccessTime) {
157 this.lastAccessTime = lastAccessTime;
158 }
159
160
161
162
163
164
165
166 public boolean isExpired() {
167 return expired;
168 }
169
170 public void setExpired(boolean expired) {
171 this.expired = expired;
172 }
173
174 public long getTimeout() {
175 return timeout;
176 }
177
178 public void setTimeout(long timeout) {
179 this.timeout = timeout;
180 }
181
182 public String getHost() {
183 return host;
184 }
185
186 public void setHost(String host) {
187 this.host = host;
188 }
189
190 public Map<Object, Object> getAttributes() {
191 return attributes;
192 }
193
194 public void setAttributes(Map<Object, Object> attributes) {
195 this.attributes = attributes;
196 }
197
198 public void touch() {
199 this.lastAccessTime = new Date();
200 }
201
202 public void stop() {
203 if (this.stopTimestamp == null) {
204 this.stopTimestamp = new Date();
205 }
206 }
207
208 protected boolean isStopped() {
209 return getStopTimestamp() != null;
210 }
211
212 protected void expire() {
213 stop();
214 this.expired = true;
215 }
216
217
218
219
220 public boolean isValid() {
221 return !isStopped() && !isExpired();
222 }
223
224
225
226
227
228
229 protected boolean isTimedOut() {
230
231 if (isExpired()) {
232 return true;
233 }
234
235 long timeout = getTimeout();
236
237 if (timeout >= 0L) {
238
239 Date lastAccessTime = getLastAccessTime();
240
241 if (lastAccessTime == null) {
242 String msg = "session.lastAccessTime for session with id ["
243 + getId() + "] is null. This value must be set at "
244 + "least once, preferably at least upon instantiation. Please check the "
245 + getClass().getName() + " implementation and ensure "
246 + "this value will be set (perhaps in the constructor?)";
247 throw new IllegalStateException(msg);
248 }
249
250
251
252
253
254
255 long expireTimeMillis = System.currentTimeMillis() - timeout;
256 Date expireTime = new Date(expireTimeMillis);
257 return lastAccessTime.before(expireTime);
258 } else {
259 if (LOGGER.isTraceEnabled()) {
260 LOGGER.trace("No timeout for session with id [" + getId()
261 + "]. Session is not considered expired.");
262 }
263 }
264
265 return false;
266 }
267
268 public void validate() throws InvalidSessionException {
269
270 if (isStopped()) {
271
272 String msg = "Session with id [" + getId() + "] has been "
273 + "explicitly stopped. No further interaction under this session is "
274 + "allowed.";
275 throw new StoppedSessionException(msg);
276 }
277
278
279 if (isTimedOut()) {
280 expire();
281
282
283 Date lastAccessTime = getLastAccessTime();
284 long timeout = getTimeout();
285
286 Serializable sessionId = getId();
287
288 DateFormat df = DateFormat.getInstance();
289 String msg = "Session with id [" + sessionId + "] has expired. "
290 + "Last access time: " + df.format(lastAccessTime)
291 + ". Current time: " + df.format(new Date())
292 + ". Session timeout is set to " + timeout / MILLIS_PER_SECOND + " seconds ("
293 + timeout / MILLIS_PER_MINUTE + " minutes)";
294 if (LOGGER.isTraceEnabled()) {
295 LOGGER.trace(msg);
296 }
297 throw new ExpiredSessionException(msg);
298 }
299 }
300
301 private Map<Object, Object> getAttributesLazy() {
302 Map<Object, Object> attributes = getAttributes();
303 if (attributes == null) {
304 attributes = new HashMap<Object, Object>();
305 setAttributes(attributes);
306 }
307 return attributes;
308 }
309
310 public Collection<Object> getAttributeKeys() throws InvalidSessionException {
311 Map<Object, Object> attributes = getAttributes();
312 if (attributes == null) {
313 return Collections.emptySet();
314 }
315 return attributes.keySet();
316 }
317
318 public Object getAttribute(Object key) {
319 Map<Object, Object> attributes = getAttributes();
320 if (attributes == null) {
321 return null;
322 }
323 return attributes.get(key);
324 }
325
326 public void setAttribute(Object key, Object value) {
327 if (value == null) {
328 removeAttribute(key);
329 } else {
330 getAttributesLazy().put(key, value);
331 }
332 }
333
334 public Object removeAttribute(Object key) {
335 Map<Object, Object> attributes = getAttributes();
336 if (attributes == null) {
337 return null;
338 } else {
339 return attributes.remove(key);
340 }
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355 @Override
356 public boolean equals(Object obj) {
357 if (this == obj) {
358 return true;
359 }
360 if (obj instanceof SimpleSession) {
361 SimpleSession other = (SimpleSession) obj;
362 Serializable thisId = getId();
363 Serializable otherId = other.getId();
364 if (thisId != null && otherId != null) {
365 return thisId.equals(otherId);
366 } else {
367
368 return onEquals(other);
369 }
370 }
371 return false;
372 }
373
374
375
376
377
378
379
380
381
382 @SuppressWarnings({"checkstyle:BooleanExpressionComplexity", "checkstyle:MethodCount"})
383 protected boolean onEquals(SimpleSession ss) {
384 return (getStartTimestamp() != null ? getStartTimestamp().equals(ss.getStartTimestamp()) : ss.getStartTimestamp() == null)
385 && (getStopTimestamp() != null ? getStopTimestamp().equals(ss.getStopTimestamp()) : ss.getStopTimestamp() == null)
386 && (getLastAccessTime() != null
387 ? getLastAccessTime().equals(ss.getLastAccessTime()) : ss.getLastAccessTime() == null)
388 && (getTimeout() == ss.getTimeout())
389 && (isExpired() == ss.isExpired())
390 && (getHost() != null ? getHost().equals(ss.getHost()) : ss.getHost() == null)
391 && (getAttributes() != null ? getAttributes().equals(ss.getAttributes()) : ss.getAttributes() == null);
392 }
393
394
395
396
397
398
399
400
401
402
403
404 @Override
405 public int hashCode() {
406 Serializable id = getId();
407 if (id != null) {
408 return id.hashCode();
409 }
410 int hashCode = getStartTimestamp() != null ? getStartTimestamp().hashCode() : 0;
411 hashCode = 31 * hashCode + (getStopTimestamp() != null ? getStopTimestamp().hashCode() : 0);
412 hashCode = 31 * hashCode + (getLastAccessTime() != null ? getLastAccessTime().hashCode() : 0);
413 hashCode = 31 * hashCode + Long.valueOf(Math.max(getTimeout(), 0)).hashCode();
414 hashCode = 31 * hashCode + Boolean.valueOf(isExpired()).hashCode();
415 hashCode = 31 * hashCode + (getHost() != null ? getHost().hashCode() : 0);
416 hashCode = 31 * hashCode + (getAttributes() != null ? getAttributes().hashCode() : 0);
417 return hashCode;
418 }
419
420
421
422
423
424
425
426
427
428 @Override
429 public String toString() {
430 StringBuilder sb = new StringBuilder();
431 sb.append(getClass().getName()).append(",id=").append(getId());
432 return sb.toString();
433 }
434
435
436
437
438
439
440
441
442 @SuppressWarnings("checkstyle:NPathComplexity")
443 private void writeObject(ObjectOutputStream out) throws IOException {
444 out.defaultWriteObject();
445 short alteredFieldsBitMask = getAlteredFieldsBitMask();
446 out.writeShort(alteredFieldsBitMask);
447 if (id != null) {
448 out.writeObject(id);
449 }
450 if (startTimestamp != null) {
451 out.writeObject(startTimestamp);
452 }
453 if (stopTimestamp != null) {
454 out.writeObject(stopTimestamp);
455 }
456 if (lastAccessTime != null) {
457 out.writeObject(lastAccessTime);
458 }
459 if (timeout != 0L) {
460 out.writeLong(timeout);
461 }
462 if (expired) {
463 out.writeBoolean(expired);
464 }
465 if (host != null) {
466 out.writeUTF(host);
467 }
468 if (!CollectionUtils.isEmpty(attributes)) {
469 out.writeObject(attributes);
470 }
471 }
472
473
474
475
476
477
478
479
480
481 @SuppressWarnings({"unchecked", "checkstyle:NPathComplexity"})
482 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
483 in.defaultReadObject();
484 short bitMask = in.readShort();
485
486 if (isFieldPresent(bitMask, ID_BIT_MASK)) {
487 this.id = (Serializable) in.readObject();
488 }
489 if (isFieldPresent(bitMask, START_TIMESTAMP_BIT_MASK)) {
490 this.startTimestamp = (Date) in.readObject();
491 }
492 if (isFieldPresent(bitMask, STOP_TIMESTAMP_BIT_MASK)) {
493 this.stopTimestamp = (Date) in.readObject();
494 }
495 if (isFieldPresent(bitMask, LAST_ACCESS_TIME_BIT_MASK)) {
496 this.lastAccessTime = (Date) in.readObject();
497 }
498 if (isFieldPresent(bitMask, TIMEOUT_BIT_MASK)) {
499 this.timeout = in.readLong();
500 }
501 if (isFieldPresent(bitMask, EXPIRED_BIT_MASK)) {
502 this.expired = in.readBoolean();
503 }
504 if (isFieldPresent(bitMask, HOST_BIT_MASK)) {
505 this.host = in.readUTF();
506 }
507 if (isFieldPresent(bitMask, ATTRIBUTES_BIT_MASK)) {
508 this.attributes = (Map<Object, Object>) in.readObject();
509 }
510 }
511
512
513
514
515
516
517
518
519
520 @SuppressWarnings("checkstyle:NPathComplexity")
521 private short getAlteredFieldsBitMask() {
522 int bitMask = 0;
523 bitMask = id != null ? bitMask | ID_BIT_MASK : bitMask;
524 bitMask = startTimestamp != null ? bitMask | START_TIMESTAMP_BIT_MASK : bitMask;
525 bitMask = stopTimestamp != null ? bitMask | STOP_TIMESTAMP_BIT_MASK : bitMask;
526 bitMask = lastAccessTime != null ? bitMask | LAST_ACCESS_TIME_BIT_MASK : bitMask;
527 bitMask = timeout != 0L ? bitMask | TIMEOUT_BIT_MASK : bitMask;
528 bitMask = expired ? bitMask | EXPIRED_BIT_MASK : bitMask;
529 bitMask = host != null ? bitMask | HOST_BIT_MASK : bitMask;
530 bitMask = !CollectionUtils.isEmpty(attributes) ? bitMask | ATTRIBUTES_BIT_MASK : bitMask;
531 return (short) bitMask;
532 }
533
534
535
536
537
538
539
540
541
542
543
544
545
546 private static boolean isFieldPresent(short bitMask, int fieldBitMask) {
547 return (bitMask & fieldBitMask) != 0;
548 }
549
550 }