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