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