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