View Javadoc

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