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