1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.nosql.mongodb;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.ObjectInputStream;
25 import java.io.ObjectOutputStream;
26 import java.net.UnknownHostException;
27 import java.util.Date;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Set;
31
32 import org.eclipse.jetty.nosql.NoSqlSession;
33 import org.eclipse.jetty.nosql.NoSqlSessionManager;
34 import org.eclipse.jetty.server.SessionIdManager;
35 import org.eclipse.jetty.util.annotation.ManagedAttribute;
36 import org.eclipse.jetty.util.annotation.ManagedObject;
37 import org.eclipse.jetty.util.annotation.ManagedOperation;
38 import org.eclipse.jetty.util.log.Log;
39 import org.eclipse.jetty.util.log.Logger;
40
41 import com.mongodb.BasicDBObject;
42 import com.mongodb.DBCollection;
43 import com.mongodb.DBObject;
44 import com.mongodb.MongoException;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 @ManagedObject("Mongo Session Manager")
97 public class MongoSessionManager extends NoSqlSessionManager
98 {
99 private static final Logger LOG = Log.getLogger(MongoSessionManager.class);
100
101 private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
102
103
104
105
106
107
108
109 private final static String __METADATA = "__metadata__";
110
111
112
113
114
115 public final static String __ID = "id";
116
117
118
119
120 private final static String __CREATED = "created";
121
122
123
124
125 public final static String __VALID = "valid";
126
127
128
129
130 public final static String __INVALIDATED = "invalidated";
131
132
133
134
135 public final static String __ACCESSED = "accessed";
136
137
138
139
140 public final static String __EXPIRY = "expiry";
141
142
143
144
145 public final static String __MAX_IDLE = "maxIdle";
146
147
148
149
150 private final static String __CONTEXT = "context";
151
152
153
154
155
156 public final static String __VERSION = __METADATA + ".version";
157
158
159
160
161 private String _contextId = null;
162
163
164
165
166
167 private DBCollection _dbSessions;
168
169
170
171
172
173 private DBObject _version_1;
174
175
176
177 public MongoSessionManager() throws UnknownHostException, MongoException
178 {
179
180 }
181
182
183
184
185 @Override
186 public void doStart() throws Exception
187 {
188 super.doStart();
189 String[] hosts = getContextHandler().getVirtualHosts();
190
191 if (hosts == null || hosts.length == 0)
192 hosts = new String[]
193 { "::" };
194
195 String contextPath = getContext().getContextPath();
196 if (contextPath == null || "".equals(contextPath))
197 {
198 contextPath = "*";
199 }
200
201 _contextId = createContextId(hosts,contextPath);
202 _version_1 = new BasicDBObject(getContextAttributeKey(__VERSION),1);
203 }
204
205
206
207
208
209 @Override
210 public void setSessionIdManager(SessionIdManager metaManager)
211 {
212 MongoSessionIdManager msim = (MongoSessionIdManager)metaManager;
213 _dbSessions=msim.getSessions();
214 super.setSessionIdManager(metaManager);
215
216 }
217
218
219 @Override
220 protected synchronized Object save(NoSqlSession session, Object version, boolean activateAfterSave)
221 {
222 try
223 {
224 __log.debug("MongoSessionManager:save session {}", session.getClusterId());
225 session.willPassivate();
226
227
228 BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
229
230
231 BasicDBObject update = new BasicDBObject();
232 boolean upsert = false;
233 BasicDBObject sets = new BasicDBObject();
234 BasicDBObject unsets = new BasicDBObject();
235
236
237
238 if (session.isValid())
239 {
240 long expiry = (session.getMaxInactiveInterval() > 0?(session.getAccessed()+(1000L*getMaxInactiveInterval())):0);
241 __log.debug("MongoSessionManager: calculated expiry {} for session {}", expiry, session.getId());
242
243
244 if (version == null)
245 {
246
247 upsert = true;
248 version = new Long(1);
249 sets.put(__CREATED,session.getCreationTime());
250 sets.put(__VALID,true);
251
252 sets.put(getContextAttributeKey(__VERSION),version);
253 sets.put(__MAX_IDLE, getMaxInactiveInterval());
254 sets.put(__EXPIRY, expiry);
255 }
256 else
257 {
258 version = new Long(((Number)version).longValue() + 1);
259 update.put("$inc",_version_1);
260
261 BasicDBObject fields = new BasicDBObject();
262 fields.append(__MAX_IDLE, true);
263 fields.append(__EXPIRY, true);
264 DBObject o = _dbSessions.findOne(new BasicDBObject("id",session.getClusterId()), fields);
265 if (o != null)
266 {
267 Integer currentMaxIdle = (Integer)o.get(__MAX_IDLE);
268 Long currentExpiry = (Long)o.get(__EXPIRY);
269 if (currentMaxIdle != null && getMaxInactiveInterval() > 0 && getMaxInactiveInterval() < currentMaxIdle)
270 sets.put(__MAX_IDLE, getMaxInactiveInterval());
271 if (currentExpiry != null && expiry > 0 && expiry < currentExpiry)
272 sets.put(__EXPIRY, currentExpiry);
273 }
274 }
275
276 sets.put(__ACCESSED,session.getAccessed());
277 Set<String> names = session.takeDirty();
278 if (isSaveAllAttributes() || upsert)
279 {
280 names.addAll(session.getNames());
281 }
282
283 for (String name : names)
284 {
285 Object value = session.getAttribute(name);
286 if (value == null)
287 unsets.put(getContextKey() + "." + encodeName(name),1);
288 else
289 sets.put(getContextKey() + "." + encodeName(name),encodeName(value));
290 }
291 }
292 else
293 {
294 sets.put(__VALID,false);
295 sets.put(__INVALIDATED, System.currentTimeMillis());
296 unsets.put(getContextKey(),1);
297 }
298
299
300 if (!sets.isEmpty())
301 update.put("$set",sets);
302 if (!unsets.isEmpty())
303 update.put("$unset",unsets);
304
305 _dbSessions.update(key,update,upsert,false);
306 __log.debug("MongoSessionManager:save:db.sessions.update( {}, {}, true) ", key, update);
307
308 if (activateAfterSave)
309 session.didActivate();
310
311 return version;
312 }
313 catch (Exception e)
314 {
315 LOG.warn(e);
316 }
317 return null;
318 }
319
320
321 @Override
322 protected Object refresh(NoSqlSession session, Object version)
323 {
324 __log.debug("MongoSessionManager:refresh session {}", session.getId());
325
326
327 if (version != null)
328 {
329 DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()),_version_1);
330
331 if (o != null)
332 {
333 Object saved = getNestedValue(o, getContextAttributeKey(__VERSION));
334
335 if (saved != null && saved.equals(version))
336 {
337 __log.debug("MongoSessionManager:refresh not needed session {}", session.getId());
338 return version;
339 }
340 version = saved;
341 }
342 }
343
344
345 DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()));
346
347
348 if (o == null)
349 {
350 __log.debug("MongoSessionManager:refresh:marking session {} invalid, no object", session.getClusterId());
351 session.invalidate();
352 return null;
353 }
354
355
356 Boolean valid = (Boolean)o.get(__VALID);
357 if (valid == null || !valid)
358 {
359 __log.debug("MongoSessionManager:refresh:marking session {} invalid, valid flag {}", session.getClusterId(), valid);
360 session.invalidate();
361 return null;
362 }
363
364
365
366 session.willPassivate();
367 try
368 {
369 DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
370
371 if (attrs == null || attrs.keySet().size() == 0)
372 {
373 session.clearAttributes();
374 }
375 else
376 {
377
378 for (String name : attrs.keySet())
379 {
380
381 if (__METADATA.equals(name))
382 continue;
383
384 String attr = decodeName(name);
385 Object value = decodeValue(attrs.get(name));
386
387
388 if (session.getAttribute(attr) == null)
389 {
390 session.doPutOrRemove(attr,value);
391 session.bindValue(attr,value);
392 }
393 else
394 {
395 session.doPutOrRemove(attr,value);
396 }
397
398 }
399
400 for (String str : session.getNames())
401 {
402 if (!attrs.keySet().contains(encodeName(str)))
403 {
404 session.doPutOrRemove(str,null);
405 session.unbindValue(str,session.getAttribute(str));
406 }
407 }
408 }
409
410
411
412
413 BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
414 BasicDBObject sets = new BasicDBObject();
415
416 BasicDBObject update = new BasicDBObject();
417 sets.put(__ACCESSED,System.currentTimeMillis());
418
419 if (!sets.isEmpty())
420 {
421 update.put("$set",sets);
422 }
423
424 _dbSessions.update(key,update,false,false);
425
426 session.didActivate();
427
428 return version;
429 }
430 catch (Exception e)
431 {
432 LOG.warn(e);
433 }
434
435 return null;
436 }
437
438
439 @Override
440 protected synchronized NoSqlSession loadSession(String clusterId)
441 {
442 DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,clusterId));
443
444 __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o);
445 if (o == null)
446 return null;
447
448 Boolean valid = (Boolean)o.get(__VALID);
449 __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid);
450 if (valid == null || !valid)
451 return null;
452
453 try
454 {
455 Object version = o.get(getContextAttributeKey(__VERSION));
456 Long created = (Long)o.get(__CREATED);
457 Long accessed = (Long)o.get(__ACCESSED);
458
459 NoSqlSession session = null;
460
461
462 DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
463
464 __log.debug("MongoSessionManager:attrs {}", attrs);
465 if (attrs != null)
466 {
467 __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey());
468
469 session = new NoSqlSession(this,created,accessed,clusterId,version);
470
471 for (String name : attrs.keySet())
472 {
473
474 if ( __METADATA.equals(name) )
475 continue;
476
477 String attr = decodeName(name);
478 Object value = decodeValue(attrs.get(name));
479
480 session.doPutOrRemove(attr,value);
481 session.bindValue(attr,value);
482 }
483 session.didActivate();
484 }
485 else
486 __log.debug("MongoSessionManager: session {} not present for context {}",clusterId, getContextKey());
487
488 return session;
489 }
490 catch (Exception e)
491 {
492 LOG.warn(e);
493 }
494 return null;
495 }
496
497
498
499
500
501
502
503
504 @Override
505 protected boolean remove(NoSqlSession session)
506 {
507 __log.debug("MongoSessionManager:remove:session {} for context {}",session.getClusterId(), getContextKey());
508
509
510
511
512
513 BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
514
515 DBObject o = _dbSessions.findOne(key,_version_1);
516
517 if (o != null)
518 {
519 BasicDBObject remove = new BasicDBObject();
520 BasicDBObject unsets = new BasicDBObject();
521 unsets.put(getContextKey(),1);
522 remove.put("$unset",unsets);
523 _dbSessions.update(key,remove);
524
525 return true;
526 }
527 else
528 {
529 return false;
530 }
531 }
532
533
534
535
536
537
538 @Override
539 protected void expire (String idInCluster)
540 {
541 __log.debug("MongoSessionManager:expire session {} ", idInCluster);
542
543
544 super.expire(idInCluster);
545
546
547 DBObject validKey = new BasicDBObject(__VALID, true);
548 DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,idInCluster), validKey);
549
550 if (o != null && (Boolean)o.get(__VALID))
551 {
552 BasicDBObject update = new BasicDBObject();
553 BasicDBObject sets = new BasicDBObject();
554 sets.put(__VALID,false);
555 sets.put(__INVALIDATED, System.currentTimeMillis());
556 update.put("$set",sets);
557
558 BasicDBObject key = new BasicDBObject(__ID,idInCluster);
559 _dbSessions.update(key,update);
560 }
561 }
562
563
564
565
566
567
568
569 @Override
570 protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception
571 {
572 BasicDBObject key = new BasicDBObject(__ID, session.getClusterId());
573 BasicDBObject sets = new BasicDBObject();
574 BasicDBObject update = new BasicDBObject(__ID, newClusterId);
575 sets.put("$set", update);
576 _dbSessions.update(key, sets, false, false);
577 }
578
579
580 protected String encodeName(String name)
581 {
582 return name.replace("%","%25").replace(".","%2E");
583 }
584
585
586 protected String decodeName(String name)
587 {
588 return name.replace("%2E",".").replace("%25","%");
589 }
590
591
592 protected Object encodeName(Object value) throws IOException
593 {
594 if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date)
595 {
596 return value;
597 }
598 else if (value.getClass().equals(HashMap.class))
599 {
600 BasicDBObject o = new BasicDBObject();
601 for (Map.Entry<?, ?> entry : ((Map<?, ?>)value).entrySet())
602 {
603 if (!(entry.getKey() instanceof String))
604 {
605 o = null;
606 break;
607 }
608 o.append(encodeName(entry.getKey().toString()),encodeName(entry.getValue()));
609 }
610
611 if (o != null)
612 return o;
613 }
614
615 ByteArrayOutputStream bout = new ByteArrayOutputStream();
616 ObjectOutputStream out = new ObjectOutputStream(bout);
617 out.reset();
618 out.writeUnshared(value);
619 out.flush();
620 return bout.toByteArray();
621 }
622
623
624 protected Object decodeValue(final Object valueToDecode) throws IOException, ClassNotFoundException
625 {
626 if (valueToDecode == null || valueToDecode instanceof Number || valueToDecode instanceof String || valueToDecode instanceof Boolean || valueToDecode instanceof Date)
627 {
628 return valueToDecode;
629 }
630 else if (valueToDecode instanceof byte[])
631 {
632 final byte[] decodeObject = (byte[])valueToDecode;
633 final ByteArrayInputStream bais = new ByteArrayInputStream(decodeObject);
634 final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais);
635 return objectInputStream.readUnshared();
636 }
637 else if (valueToDecode instanceof DBObject)
638 {
639 Map<String, Object> map = new HashMap<String, Object>();
640 for (String name : ((DBObject)valueToDecode).keySet())
641 {
642 String attr = decodeName(name);
643 map.put(attr,decodeValue(((DBObject)valueToDecode).get(name)));
644 }
645 return map;
646 }
647 else
648 {
649 throw new IllegalStateException(valueToDecode.getClass().toString());
650 }
651 }
652
653
654
655 private String getContextKey()
656 {
657 return __CONTEXT + "." + _contextId;
658 }
659
660
661
662
663
664
665 private String getContextAttributeKey(String attr)
666 {
667 return getContextKey()+ "." + attr;
668 }
669
670 @ManagedOperation(value="purge invalid sessions in the session store based on normal criteria", impact="ACTION")
671 public void purge()
672 {
673 ((MongoSessionIdManager)_sessionIdManager).purge();
674 }
675
676
677 @ManagedOperation(value="full purge of invalid sessions in the session store", impact="ACTION")
678 public void purgeFully()
679 {
680 ((MongoSessionIdManager)_sessionIdManager).purgeFully();
681 }
682
683 @ManagedOperation(value="scavenge sessions known to this manager", impact="ACTION")
684 public void scavenge()
685 {
686 ((MongoSessionIdManager)_sessionIdManager).scavenge();
687 }
688
689 @ManagedOperation(value="scanvenge all sessions", impact="ACTION")
690 public void scavengeFully()
691 {
692 ((MongoSessionIdManager)_sessionIdManager).scavengeFully();
693 }
694
695
696
697
698
699
700
701
702 @ManagedAttribute("total number of known sessions in the store")
703 public long getSessionStoreCount()
704 {
705 return _dbSessions.find().count();
706 }
707
708
709
710
711
712
713
714
715
716 private String createContextId(String[] virtualHosts, String contextPath)
717 {
718 String contextId = virtualHosts[0] + contextPath;
719
720 contextId.replace('/', '_');
721 contextId.replace('.','_');
722 contextId.replace('\\','_');
723
724 return contextId;
725 }
726
727
728
729
730 private Object getNestedValue(DBObject dbObject, String nestedKey)
731 {
732 String[] keyChain = nestedKey.split("\\.");
733
734 DBObject temp = dbObject;
735
736 for (int i = 0; i < keyChain.length - 1; ++i)
737 {
738 temp = (DBObject)temp.get(keyChain[i]);
739
740 if ( temp == null )
741 {
742 return null;
743 }
744 }
745
746 return temp.get(keyChain[keyChain.length - 1]);
747 }
748
749
750
751
752
753
754
755 protected class ClassLoadingObjectInputStream extends ObjectInputStream
756 {
757 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
758 {
759 super(in);
760 }
761
762 public ClassLoadingObjectInputStream () throws IOException
763 {
764 super();
765 }
766
767 @Override
768 public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
769 {
770 try
771 {
772 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
773 }
774 catch (ClassNotFoundException e)
775 {
776 return super.resolveClass(cl);
777 }
778 }
779 }
780
781
782 }