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 (version == null)
142 {
143
144 upsert = true;
145 version = new Long(1);
146 sets.put(__CREATED,session.getCreationTime());
147 sets.put(__VALID,true);
148 sets.put(getContextKey(__VERSION),version);
149 }
150 else
151 {
152 version = new Long(((Long)version).intValue() + 1);
153 update.put("$inc",__version_1);
154 }
155
156
157 if (session.isValid())
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 if (attrs != null)
257 {
258 for (String name : attrs.keySet())
259 {
260 if (__METADATA.equals(name))
261 {
262 continue;
263 }
264
265 String attr = decodeName(name);
266 Object value = decodeValue(attrs.get(name));
267
268 if (attrs.keySet().contains(name))
269 {
270 session.doPutOrRemove(attr,value);
271 session.bindValue(attr,value);
272 }
273 else
274 {
275 session.doPutOrRemove(attr,value);
276 }
277 }
278
279 for (String name : session.getNames())
280 {
281 if (!attrs.keySet().contains(name))
282 {
283 session.doPutOrRemove(name,null);
284 session.unbindValue(name,session.getAttribute(name));
285 }
286 }
287 }
288
289 session.didActivate();
290
291 return version;
292 }
293 catch (Exception e)
294 {
295 LOG.warn(e);
296 }
297
298 return null;
299 }
300
301
302 @Override
303 protected synchronized NoSqlSession loadSession(String clusterId)
304 {
305 DBObject o = _sessions.findOne(new BasicDBObject(__ID,clusterId));
306
307 __log.debug("MongoSessionManager:loaded " + o);
308
309 if (o == null)
310 {
311 return null;
312 }
313
314 Boolean valid = (Boolean)o.get(__VALID);
315 if (valid == null || !valid)
316 {
317 return null;
318 }
319
320 try
321 {
322 Object version = o.get(getContextKey(__VERSION));
323 Long created = (Long)o.get(__CREATED);
324 Long accessed = (Long)o.get(__ACCESSED);
325
326 NoSqlSession session = new NoSqlSession(this,created,accessed,clusterId,version);
327
328
329 DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
330
331 __log.debug("MongoSessionManager:attrs: " + attrs);
332 if (attrs != null)
333 {
334 for (String name : attrs.keySet())
335 {
336 if ( __METADATA.equals(name) )
337 {
338 continue;
339 }
340
341 String attr = decodeName(name);
342 Object value = decodeValue(attrs.get(name));
343
344 session.doPutOrRemove(attr,value);
345 session.bindValue(attr,value);
346
347 }
348 }
349 session.didActivate();
350
351 return session;
352 }
353 catch (Exception e)
354 {
355 LOG.warn(e);
356 }
357 return null;
358 }
359
360
361 @Override
362 protected boolean remove(NoSqlSession session)
363 {
364 __log.debug("MongoSessionManager:remove:session " + session.getClusterId());
365
366
367
368
369
370 BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
371
372 DBObject o = _sessions.findOne(key,__version_1);
373
374 if (o != null)
375 {
376 BasicDBObject remove = new BasicDBObject();
377 BasicDBObject unsets = new BasicDBObject();
378 unsets.put(getContextKey(),1);
379 remove.put("$unset",unsets);
380 _sessions.update(key,remove);
381
382 return true;
383 }
384 else
385 {
386 return false;
387 }
388 }
389
390
391 @Override
392 protected void invalidateSession(String idInCluster)
393 {
394 __log.debug("MongoSessionManager:invalidateSession:invalidating " + idInCluster);
395
396 super.invalidateSession(idInCluster);
397
398
399
400
401
402 DBObject validKey = new BasicDBObject(__VALID, true);
403 DBObject o = _sessions.findOne(new BasicDBObject(__ID,idInCluster), validKey);
404
405 if (o != null && (Boolean)o.get(__VALID))
406 {
407 BasicDBObject update = new BasicDBObject();
408 BasicDBObject sets = new BasicDBObject();
409 sets.put(__VALID,false);
410 sets.put(__INVALIDATED, System.currentTimeMillis());
411 update.put("$set",sets);
412
413 BasicDBObject key = new BasicDBObject(__ID,idInCluster);
414
415 _sessions.update(key,update);
416 }
417 }
418
419
420 @Override
421 protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception
422 {
423
424 BasicDBObject key = new BasicDBObject(__ID, session.getClusterId());
425 BasicDBObject sets = new BasicDBObject();
426 BasicDBObject update = new BasicDBObject(__ID, newClusterId);
427 sets.put("$set", update);
428 _sessions.update(key, sets, false, false);
429 }
430
431
432 protected String encodeName(String name)
433 {
434 return name.replace("%","%25").replace(".","%2E");
435 }
436
437
438 protected String decodeName(String name)
439 {
440 return name.replace("%2E",".").replace("%25","%");
441 }
442
443
444 protected Object encodeName(Object value) throws IOException
445 {
446 if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date)
447 {
448 return value;
449 }
450 else if (value.getClass().equals(HashMap.class))
451 {
452 BasicDBObject o = new BasicDBObject();
453 for (Map.Entry<?, ?> entry : ((Map<?, ?>)value).entrySet())
454 {
455 if (!(entry.getKey() instanceof String))
456 {
457 o = null;
458 break;
459 }
460 o.append(encodeName(entry.getKey().toString()),encodeName(entry.getValue()));
461 }
462
463 if (o != null)
464 return o;
465 }
466
467 ByteArrayOutputStream bout = new ByteArrayOutputStream();
468 ObjectOutputStream out = new ObjectOutputStream(bout);
469 out.reset();
470 out.writeUnshared(value);
471 out.flush();
472 return bout.toByteArray();
473 }
474
475
476 protected Object decodeValue(final Object valueToDecode) throws IOException, ClassNotFoundException
477 {
478 if (valueToDecode == null || valueToDecode instanceof Number || valueToDecode instanceof String || valueToDecode instanceof Boolean || valueToDecode instanceof Date)
479 {
480 return valueToDecode;
481 }
482 else if (valueToDecode instanceof byte[])
483 {
484 final byte[] decodeObject = (byte[])valueToDecode;
485 final ByteArrayInputStream bais = new ByteArrayInputStream(decodeObject);
486 final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais);
487 return objectInputStream.readUnshared();
488 }
489 else if (valueToDecode instanceof DBObject)
490 {
491 Map<String, Object> map = new HashMap<String, Object>();
492 for (String name : ((DBObject)valueToDecode).keySet())
493 {
494 String attr = decodeName(name);
495 map.put(attr,decodeValue(((DBObject)valueToDecode).get(name)));
496 }
497 return map;
498 }
499 else
500 {
501 throw new IllegalStateException(valueToDecode.getClass().toString());
502 }
503 }
504
505
506
507 private String getContextKey()
508 {
509 return __CONTEXT + "." + _contextId;
510 }
511
512
513 private String getContextKey(String keybit)
514 {
515 return __CONTEXT + "." + _contextId + "." + keybit;
516 }
517
518 @ManagedOperation(value="purge invalid sessions in the session store based on normal criteria", impact="ACTION")
519 public void purge()
520 {
521 ((MongoSessionIdManager)_sessionIdManager).purge();
522 }
523
524
525 @ManagedOperation(value="full purge of invalid sessions in the session store", impact="ACTION")
526 public void purgeFully()
527 {
528 ((MongoSessionIdManager)_sessionIdManager).purgeFully();
529 }
530
531 @ManagedOperation(value="scavenge sessions known to this manager", impact="ACTION")
532 public void scavenge()
533 {
534 ((MongoSessionIdManager)_sessionIdManager).scavenge();
535 }
536
537 @ManagedOperation(value="scanvenge all sessions", impact="ACTION")
538 public void scavengeFully()
539 {
540 ((MongoSessionIdManager)_sessionIdManager).scavengeFully();
541 }
542
543
544
545
546
547
548
549
550 @ManagedAttribute("total number of known sessions in the store")
551 public long getSessionStoreCount()
552 {
553 return _sessions.find().count();
554 }
555
556
557
558
559
560
561
562
563
564 private String createContextId(String[] virtualHosts, String contextPath)
565 {
566 String contextId = virtualHosts[0] + contextPath;
567
568 contextId.replace('/', '_');
569 contextId.replace('.','_');
570 contextId.replace('\\','_');
571
572 return contextId;
573 }
574
575
576
577
578 private Object getNestedValue(DBObject dbObject, String nestedKey)
579 {
580 String[] keyChain = nestedKey.split("\\.");
581
582 DBObject temp = dbObject;
583
584 for (int i = 0; i < keyChain.length - 1; ++i)
585 {
586 temp = (DBObject)temp.get(keyChain[i]);
587
588 if ( temp == null )
589 {
590 return null;
591 }
592 }
593
594 return temp.get(keyChain[keyChain.length - 1]);
595 }
596
597
598
599
600
601
602
603 protected class ClassLoadingObjectInputStream extends ObjectInputStream
604 {
605 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
606 {
607 super(in);
608 }
609
610 public ClassLoadingObjectInputStream () throws IOException
611 {
612 super();
613 }
614
615 @Override
616 public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
617 {
618 try
619 {
620 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
621 }
622 catch (ClassNotFoundException e)
623 {
624 return super.resolveClass(cl);
625 }
626 }
627 }
628
629
630 }