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