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