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()+(1000*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 session.clearAttributes();
370
371 DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
372
373 if (attrs != null)
374 {
375 for (String name : attrs.keySet())
376 {
377
378 if (__METADATA.equals(name))
379 continue;
380
381 String attr = decodeName(name);
382 Object value = decodeValue(attrs.get(name));
383
384 if (attrs.keySet().contains(name))
385 {
386 session.doPutOrRemove(attr,value);
387 session.bindValue(attr,value);
388 }
389 else
390 {
391 session.doPutOrRemove(attr,value);
392 }
393 }
394
395 for (String name : session.getNames())
396 {
397 if (!attrs.keySet().contains(name))
398 {
399 session.doPutOrRemove(name,null);
400 session.unbindValue(name,session.getAttribute(name));
401 }
402 }
403 }
404
405
406
407
408 BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
409 BasicDBObject sets = new BasicDBObject();
410
411 BasicDBObject update = new BasicDBObject();
412 sets.put(__ACCESSED,System.currentTimeMillis());
413
414 if (!sets.isEmpty())
415 {
416 update.put("$set",sets);
417 }
418
419 _dbSessions.update(key,update,false,false);
420
421 session.didActivate();
422
423 return version;
424 }
425 catch (Exception e)
426 {
427 LOG.warn(e);
428 }
429
430 return null;
431 }
432
433
434 @Override
435 protected synchronized NoSqlSession loadSession(String clusterId)
436 {
437 DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,clusterId));
438
439 __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o);
440 if (o == null)
441 return null;
442
443 Boolean valid = (Boolean)o.get(__VALID);
444 __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid);
445 if (valid == null || !valid)
446 return null;
447
448 try
449 {
450 Object version = o.get(getContextAttributeKey(__VERSION));
451 Long created = (Long)o.get(__CREATED);
452 Long accessed = (Long)o.get(__ACCESSED);
453
454 NoSqlSession session = null;
455
456
457 DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
458
459 __log.debug("MongoSessionManager:attrs {}", attrs);
460 if (attrs != null)
461 {
462 __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey());
463
464 session = new NoSqlSession(this,created,accessed,clusterId,version);
465
466 for (String name : attrs.keySet())
467 {
468
469 if ( __METADATA.equals(name) )
470 continue;
471
472 String attr = decodeName(name);
473 Object value = decodeValue(attrs.get(name));
474
475 session.doPutOrRemove(attr,value);
476 session.bindValue(attr,value);
477 }
478 session.didActivate();
479 }
480 else
481 __log.debug("MongoSessionManager: session {} not present for context {}",clusterId, getContextKey());
482
483 return session;
484 }
485 catch (Exception e)
486 {
487 LOG.warn(e);
488 }
489 return null;
490 }
491
492
493
494
495
496
497
498
499 @Override
500 protected boolean remove(NoSqlSession session)
501 {
502 __log.debug("MongoSessionManager:remove:session {} for context {}",session.getClusterId(), getContextKey());
503
504
505
506
507
508 BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
509
510 DBObject o = _dbSessions.findOne(key,_version_1);
511
512 if (o != null)
513 {
514 BasicDBObject remove = new BasicDBObject();
515 BasicDBObject unsets = new BasicDBObject();
516 unsets.put(getContextKey(),1);
517 remove.put("$unset",unsets);
518 _dbSessions.update(key,remove);
519
520 return true;
521 }
522 else
523 {
524 return false;
525 }
526 }
527
528
529
530
531
532
533 @Override
534 protected void expire (String idInCluster)
535 {
536 __log.debug("MongoSessionManager:expire session {} ", idInCluster);
537
538
539 super.expire(idInCluster);
540
541
542 DBObject validKey = new BasicDBObject(__VALID, true);
543 DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,idInCluster), validKey);
544
545 if (o != null && (Boolean)o.get(__VALID))
546 {
547 BasicDBObject update = new BasicDBObject();
548 BasicDBObject sets = new BasicDBObject();
549 sets.put(__VALID,false);
550 sets.put(__INVALIDATED, System.currentTimeMillis());
551 update.put("$set",sets);
552
553 BasicDBObject key = new BasicDBObject(__ID,idInCluster);
554 _dbSessions.update(key,update);
555 }
556 }
557
558
559
560
561
562
563
564 @Override
565 protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception
566 {
567 BasicDBObject key = new BasicDBObject(__ID, session.getClusterId());
568 BasicDBObject sets = new BasicDBObject();
569 BasicDBObject update = new BasicDBObject(__ID, newClusterId);
570 sets.put("$set", update);
571 _dbSessions.update(key, sets, false, false);
572 }
573
574
575 protected String encodeName(String name)
576 {
577 return name.replace("%","%25").replace(".","%2E");
578 }
579
580
581 protected String decodeName(String name)
582 {
583 return name.replace("%2E",".").replace("%25","%");
584 }
585
586
587 protected Object encodeName(Object value) throws IOException
588 {
589 if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date)
590 {
591 return value;
592 }
593 else if (value.getClass().equals(HashMap.class))
594 {
595 BasicDBObject o = new BasicDBObject();
596 for (Map.Entry<?, ?> entry : ((Map<?, ?>)value).entrySet())
597 {
598 if (!(entry.getKey() instanceof String))
599 {
600 o = null;
601 break;
602 }
603 o.append(encodeName(entry.getKey().toString()),encodeName(entry.getValue()));
604 }
605
606 if (o != null)
607 return o;
608 }
609
610 ByteArrayOutputStream bout = new ByteArrayOutputStream();
611 ObjectOutputStream out = new ObjectOutputStream(bout);
612 out.reset();
613 out.writeUnshared(value);
614 out.flush();
615 return bout.toByteArray();
616 }
617
618
619 protected Object decodeValue(final Object valueToDecode) throws IOException, ClassNotFoundException
620 {
621 if (valueToDecode == null || valueToDecode instanceof Number || valueToDecode instanceof String || valueToDecode instanceof Boolean || valueToDecode instanceof Date)
622 {
623 return valueToDecode;
624 }
625 else if (valueToDecode instanceof byte[])
626 {
627 final byte[] decodeObject = (byte[])valueToDecode;
628 final ByteArrayInputStream bais = new ByteArrayInputStream(decodeObject);
629 final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais);
630 return objectInputStream.readUnshared();
631 }
632 else if (valueToDecode instanceof DBObject)
633 {
634 Map<String, Object> map = new HashMap<String, Object>();
635 for (String name : ((DBObject)valueToDecode).keySet())
636 {
637 String attr = decodeName(name);
638 map.put(attr,decodeValue(((DBObject)valueToDecode).get(name)));
639 }
640 return map;
641 }
642 else
643 {
644 throw new IllegalStateException(valueToDecode.getClass().toString());
645 }
646 }
647
648
649
650 private String getContextKey()
651 {
652 return __CONTEXT + "." + _contextId;
653 }
654
655
656
657
658
659
660 private String getContextAttributeKey(String attr)
661 {
662 return getContextKey()+ "." + attr;
663 }
664
665 @ManagedOperation(value="purge invalid sessions in the session store based on normal criteria", impact="ACTION")
666 public void purge()
667 {
668 ((MongoSessionIdManager)_sessionIdManager).purge();
669 }
670
671
672 @ManagedOperation(value="full purge of invalid sessions in the session store", impact="ACTION")
673 public void purgeFully()
674 {
675 ((MongoSessionIdManager)_sessionIdManager).purgeFully();
676 }
677
678 @ManagedOperation(value="scavenge sessions known to this manager", impact="ACTION")
679 public void scavenge()
680 {
681 ((MongoSessionIdManager)_sessionIdManager).scavenge();
682 }
683
684 @ManagedOperation(value="scanvenge all sessions", impact="ACTION")
685 public void scavengeFully()
686 {
687 ((MongoSessionIdManager)_sessionIdManager).scavengeFully();
688 }
689
690
691
692
693
694
695
696
697 @ManagedAttribute("total number of known sessions in the store")
698 public long getSessionStoreCount()
699 {
700 return _dbSessions.find().count();
701 }
702
703
704
705
706
707
708
709
710
711 private String createContextId(String[] virtualHosts, String contextPath)
712 {
713 String contextId = virtualHosts[0] + contextPath;
714
715 contextId.replace('/', '_');
716 contextId.replace('.','_');
717 contextId.replace('\\','_');
718
719 return contextId;
720 }
721
722
723
724
725 private Object getNestedValue(DBObject dbObject, String nestedKey)
726 {
727 String[] keyChain = nestedKey.split("\\.");
728
729 DBObject temp = dbObject;
730
731 for (int i = 0; i < keyChain.length - 1; ++i)
732 {
733 temp = (DBObject)temp.get(keyChain[i]);
734
735 if ( temp == null )
736 {
737 return null;
738 }
739 }
740
741 return temp.get(keyChain[keyChain.length - 1]);
742 }
743
744
745
746
747
748
749
750 protected class ClassLoadingObjectInputStream extends ObjectInputStream
751 {
752 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
753 {
754 super(in);
755 }
756
757 public ClassLoadingObjectInputStream () throws IOException
758 {
759 super();
760 }
761
762 @Override
763 public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
764 {
765 try
766 {
767 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
768 }
769 catch (ClassNotFoundException e)
770 {
771 return super.resolveClass(cl);
772 }
773 }
774 }
775
776
777 }