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