View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
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   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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.annotation.ManagedAttribute;
36  import org.eclipse.jetty.util.annotation.ManagedObject;
37  import org.eclipse.jetty.util.annotation.ManagedOperation;
38  import org.eclipse.jetty.util.log.Log;
39  import org.eclipse.jetty.util.log.Logger;
40  
41  import com.mongodb.BasicDBObject;
42  import com.mongodb.DBCollection;
43  import com.mongodb.DBObject;
44  import com.mongodb.MongoException;
45  import com.mongodb.WriteConcern;
46  import com.mongodb.WriteResult;
47  
48  
49  /**
50   * MongoSessionManager
51   *
52   * Clustered session manager using MongoDB as the shared DB instance.
53   * The document model is an outer object that contains the elements:
54   * <ul>
55   *  <li>"id"      : session_id </li>
56   *  <li>"created" : create_time </li>
57   *  <li>"accessed": last_access_time </li>
58   *  <li>"maxIdle" : max_idle_time setting as session was created </li>
59   *  <li>"expiry"  : time at which session should expire </li>
60   *  <li>"valid"   : session_valid </li>
61   *  <li>"context" : a nested object containing 1 nested object per context for which the session id is in use
62   * </ul>
63   * Each of the nested objects inside the "context" element contains:
64   * <ul>
65   *  <li>unique_context_name : nested object containing name:value pairs of the session attributes for that context</li>
66   * </ul>
67   * <p>
68   * One of the name:value attribute pairs will always be the special attribute "__metadata__". The value 
69   * is an object representing a version counter which is incremented every time the attributes change.
70   * </p>
71   * <p>
72   * For example:
73   * <code>
74   * { "_id"       : ObjectId("52845534a40b66410f228f23"), 
75   *    "accessed" :  NumberLong("1384818548903"), 
76   *    "maxIdle"  : 1,
77   *    "context"  : { "::/contextA" : { "A"            : "A", 
78   *                                     "__metadata__" : { "version" : NumberLong(2) } 
79   *                                   },
80   *                   "::/contextB" : { "B"            : "B", 
81   *                                     "__metadata__" : { "version" : NumberLong(1) } 
82   *                                   } 
83   *                 }, 
84   *    "created"  : NumberLong("1384818548903"),
85   *    "expiry"   : NumberLong("1384818549903"),
86   *    "id"       : "w01ijx2vnalgv1sqrpjwuirprp7", 
87   *    "valid"    : true 
88   * }
89   * </code>
90   * </p>
91   * <p>
92   * In MongoDB, the nesting level is indicated by "." separators for the key name. Thus to
93   * interact with a session attribute, the key is composed of:
94   * "context".unique_context_name.attribute_name
95   *  Eg  "context"."::/contextA"."A"
96   *  </p>
97   */
98  @ManagedObject("Mongo Session Manager")
99  public class MongoSessionManager extends NoSqlSessionManager
100 {
101     private static final Logger LOG = Log.getLogger(MongoSessionManager.class);
102   
103     private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session");
104    
105     /*
106      * strings used as keys or parts of keys in mongo
107      */
108     /**
109      * Special attribute for a session that is context-specific
110      */
111     private final static String __METADATA = "__metadata__";
112 
113     
114     /**
115      * Session id
116      */
117     public final static String __ID = "id";
118     
119     /**
120      * Time of session creation
121      */
122     private final static String __CREATED = "created";
123     
124     /**
125      * Whether or not session is valid
126      */
127     public final static String __VALID = "valid";
128     
129     /**
130      * Time at which session was invalidated
131      */
132     public final static String __INVALIDATED = "invalidated";
133     
134     /**
135      * Last access time of session
136      */
137     public final static String __ACCESSED = "accessed";
138     
139     /**
140      * Time this session will expire, based on last access time and maxIdle
141      */
142     public final static String __EXPIRY = "expiry";
143     
144     /**
145      * The max idle time of a session (smallest value across all contexts which has a session with the same id)
146      */
147     public final static String __MAX_IDLE = "maxIdle";
148     
149     /**
150      * Name of nested document field containing 1 sub document per context for which the session id is in use
151      */
152     private final static String __CONTEXT = "context";   
153     
154     
155     /**
156      * Special attribute per session per context, incremented each time attributes are modified
157      */
158     public final static String __VERSION = __METADATA + ".version";
159 
160     /**
161     * the context id is only set when this class has been started
162     */
163     private String _contextId = null;
164 
165     
166     /**
167      * Access to MongoDB
168      */
169     private DBCollection _dbSessions;
170     
171     
172     /**
173      * Utility value of 1 for a session version for this context
174      */
175     private DBObject _version_1;
176 
177 
178     /* ------------------------------------------------------------ */
179     public MongoSessionManager() throws UnknownHostException, MongoException
180     {
181         
182     }
183     
184     
185     
186     /*------------------------------------------------------------ */
187     @Override
188     public void doStart() throws Exception
189     {
190         super.doStart();
191         String[] hosts = getContextHandler().getVirtualHosts();
192 
193         if (hosts == null || hosts.length == 0)
194             hosts = new String[]
195             { "::" }; // IPv6 equiv of 0.0.0.0
196 
197         String contextPath = getContext().getContextPath();
198         if (contextPath == null || "".equals(contextPath))
199         {
200             contextPath = "*";
201         }
202 
203         _contextId = createContextId(hosts,contextPath);
204         _version_1 = new BasicDBObject(getContextAttributeKey(__VERSION),1);
205     }
206 
207     /* ------------------------------------------------------------ */
208     /**
209      * @see org.eclipse.jetty.server.session.AbstractSessionManager#setSessionIdManager(org.eclipse.jetty.server.SessionIdManager)
210      */
211     @Override
212     public void setSessionIdManager(SessionIdManager metaManager)
213     {
214         MongoSessionIdManager msim = (MongoSessionIdManager)metaManager;
215         _dbSessions=msim.getSessions();
216         super.setSessionIdManager(metaManager);
217         
218     }
219 
220     /* ------------------------------------------------------------ */
221     @Override
222     protected synchronized Object save(NoSqlSession session, Object version, boolean activateAfterSave)
223     {
224         try
225         {
226             __log.debug("MongoSessionManager:save session {}", session.getClusterId());
227             session.willPassivate();
228 
229             // Form query for upsert
230             BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
231 
232             // Form updates
233             BasicDBObject update = new BasicDBObject();
234             boolean upsert = false;
235             BasicDBObject sets = new BasicDBObject();
236             BasicDBObject unsets = new BasicDBObject();
237 
238             
239             // handle valid or invalid
240             if (session.isValid())
241             {
242                 long expiry = (session.getMaxInactiveInterval() > 0?(session.getAccessed()+(1000L*getMaxInactiveInterval())):0);
243                 __log.debug("MongoSessionManager: calculated expiry {} for session {}", expiry, session.getId());
244                 
245                 // handle new or existing
246                 if (version == null)
247                 {
248                     // New session
249                     upsert = true;
250                     version = new Long(1);
251                     sets.put(__CREATED,session.getCreationTime());
252                     sets.put(__VALID,true);
253                    
254                     sets.put(getContextAttributeKey(__VERSION),version);
255                     sets.put(__MAX_IDLE, getMaxInactiveInterval());
256                     sets.put(__EXPIRY, expiry);
257                 }
258                 else
259                 {
260                     version = new Long(((Number)version).longValue() + 1);
261                     update.put("$inc",_version_1); 
262                     //if max idle time and/or expiry is smaller for this context, then choose that for the whole session doc
263                     BasicDBObject fields = new BasicDBObject();
264                     fields.append(__MAX_IDLE, true);
265                     fields.append(__EXPIRY, true);
266                     DBObject o = _dbSessions.findOne(new BasicDBObject("id",session.getClusterId()), fields);
267                     if (o != null)
268                     {
269                         Integer currentMaxIdle = (Integer)o.get(__MAX_IDLE);
270                         Long currentExpiry = (Long)o.get(__EXPIRY);
271                         if (currentMaxIdle != null && getMaxInactiveInterval() > 0 && getMaxInactiveInterval() < currentMaxIdle)
272                             sets.put(__MAX_IDLE, getMaxInactiveInterval());
273                         if (currentExpiry != null && expiry > 0 && expiry != currentExpiry)
274                             sets.put(__EXPIRY, expiry);
275                     }
276                 }
277                 
278                 sets.put(__ACCESSED,session.getAccessed());
279                 Set<String> names = session.takeDirty();
280                 if (isSaveAllAttributes() || upsert)
281                 {
282                     names.addAll(session.getNames()); // note dirty may include removed names
283                 }
284                     
285                 for (String name : names)
286                 {
287                     Object value = session.getAttribute(name);
288                     if (value == null)
289                         unsets.put(getContextKey() + "." + encodeName(name),1);
290                     else
291                         sets.put(getContextKey() + "." + encodeName(name),encodeName(value));
292                 }
293             }
294             else
295             {
296                 sets.put(__VALID,false);
297                 sets.put(__INVALIDATED, System.currentTimeMillis());
298                 unsets.put(getContextKey(),1); 
299             }
300 
301             // Do the upsert
302             if (!sets.isEmpty())
303                 update.put("$set",sets);
304             if (!unsets.isEmpty())
305                 update.put("$unset",unsets);
306 
307             _dbSessions.update(key,update,upsert,false,WriteConcern.SAFE);
308 
309             if (__log.isDebugEnabled())
310                 __log.debug("MongoSessionManager:save:db.sessions.update( {}, {} )", key, update);
311            
312             if (activateAfterSave)
313                 session.didActivate();
314 
315             return version;
316         }
317         catch (Exception e)
318         {
319             LOG.warn(e);
320         }
321         return null;
322     }
323 
324     /*------------------------------------------------------------ */
325     @Override
326     protected Object refresh(NoSqlSession session, Object version)
327     {
328         __log.debug("MongoSessionManager:refresh session {}", session.getId());
329 
330         // check if our in memory version is the same as what is on the disk
331         if (version != null)
332         {
333             DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()),_version_1);
334 
335             if (o != null)
336             {
337                 Object saved = getNestedValue(o, getContextAttributeKey(__VERSION));
338                 
339                 if (saved != null && saved.equals(version))
340                 {
341                     __log.debug("MongoSessionManager:refresh not needed session {}", session.getId());
342                     return version;
343                 }
344                 version = saved;
345             }
346         }
347 
348         // If we are here, we have to load the object
349         DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()));
350 
351         // If it doesn't exist, invalidate
352         if (o == null)
353         {
354             __log.debug("MongoSessionManager:refresh:marking session {} invalid, no object", session.getClusterId());
355             session.invalidate();
356             return null;
357         }
358         
359         // If it has been flagged invalid, invalidate
360         Boolean valid = (Boolean)o.get(__VALID);
361         if (valid == null || !valid)
362         {
363             __log.debug("MongoSessionManager:refresh:marking session {} invalid, valid flag {}", session.getClusterId(), valid);
364             session.invalidate();
365             return null;
366         }
367 
368         // We need to update the attributes. We will model this as a passivate,
369         // followed by bindings and then activation.
370         session.willPassivate();
371         try
372         {     
373             DBObject attrs = (DBObject)getNestedValue(o,getContextKey());    
374             //if disk version now has no attributes, get rid of them
375             if (attrs == null || attrs.keySet().size() == 0)
376             {
377                 session.clearAttributes();
378             }
379             else
380             {
381                 //iterate over the names of the attributes on the disk version, updating the value
382                 for (String name : attrs.keySet())
383                 {
384                     //skip special metadata field which is not one of the session attributes
385                     if (__METADATA.equals(name))
386                         continue;
387 
388                     String attr = decodeName(name);
389                     Object value = decodeValue(attrs.get(name));
390 
391                     //session does not already contain this attribute, so bind it
392                     if (session.getAttribute(attr) == null)
393                     { 
394                         session.doPutOrRemove(attr,value);
395                         session.bindValue(attr,value);
396                     }
397                     else //session already contains this attribute, update its value
398                     {
399                         session.doPutOrRemove(attr,value);
400                     }
401 
402                 }
403                 // cleanup, remove values from session, that don't exist in data anymore:
404                 for (String str : session.getNames())
405                 {
406                    if (!attrs.keySet().contains(encodeName(str)))
407                    {
408                         session.doPutOrRemove(str,null);
409                         session.unbindValue(str,session.getAttribute(str));
410                     }
411                 }
412             }
413 
414             /*
415              * We are refreshing so we should update the last accessed time.
416              */
417             BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
418             BasicDBObject sets = new BasicDBObject();
419             // Form updates
420             BasicDBObject update = new BasicDBObject();
421             sets.put(__ACCESSED,System.currentTimeMillis());
422             // Do the upsert
423             if (!sets.isEmpty())
424             {
425                 update.put("$set",sets);
426             }            
427             
428             _dbSessions.update(key,update,false,false,WriteConcern.SAFE);
429             
430             session.didActivate();
431 
432             return version;
433         }
434         catch (Exception e)
435         {
436             LOG.warn(e);
437         }
438 
439         return null;
440     }
441 
442     /*------------------------------------------------------------ */
443     @Override
444     protected synchronized NoSqlSession loadSession(String clusterId)
445     {
446         DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,clusterId));
447         
448         __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o);
449         if (o == null)
450             return null;
451         
452         Boolean valid = (Boolean)o.get(__VALID);
453         __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid);
454         if (valid == null || !valid)
455             return null;
456         
457         try
458         {
459             Object version = o.get(getContextAttributeKey(__VERSION));
460             Long created = (Long)o.get(__CREATED);
461             Long accessed = (Long)o.get(__ACCESSED);
462           
463             NoSqlSession session = null;
464 
465             // get the session for the context
466             DBObject attrs = (DBObject)getNestedValue(o,getContextKey());
467 
468             __log.debug("MongoSessionManager:attrs {}", attrs);
469             if (attrs != null)
470             {
471                 __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey());
472                 //only load a session if it exists for this context
473                 session = new NoSqlSession(this,created,accessed,clusterId,version);
474                 
475                 for (String name : attrs.keySet())
476                 {
477                     //skip special metadata attribute which is not one of the actual session attributes
478                     if ( __METADATA.equals(name) )
479                         continue;
480                     
481                     String attr = decodeName(name);
482                     Object value = decodeValue(attrs.get(name));
483 
484                     session.doPutOrRemove(attr,value);
485                     session.bindValue(attr,value);
486                 }
487                 session.didActivate();
488             }
489             else
490                 __log.debug("MongoSessionManager: session  {} not present for context {}",clusterId, getContextKey());        
491 
492             return session;
493         }
494         catch (Exception e)
495         {
496             LOG.warn(e);
497         }
498         return null;
499     }
500 
501     
502     
503     /*------------------------------------------------------------ */
504     /** 
505      * Remove the per-context sub document for this session id.
506      * @see org.eclipse.jetty.nosql.NoSqlSessionManager#remove(org.eclipse.jetty.nosql.NoSqlSession)
507      */
508     @Override
509     protected boolean remove(NoSqlSession session)
510     {
511         __log.debug("MongoSessionManager:remove:session {} for context {}",session.getClusterId(), getContextKey());
512 
513         /*
514          * Check if the session exists and if it does remove the context
515          * associated with this session
516          */
517         BasicDBObject key = new BasicDBObject(__ID,session.getClusterId());
518         
519         DBObject o = _dbSessions.findOne(key,_version_1);
520 
521         if (o != null)
522         {
523             BasicDBObject remove = new BasicDBObject();
524             BasicDBObject unsets = new BasicDBObject();
525             unsets.put(getContextKey(),1);
526             remove.put("$unset",unsets);
527             _dbSessions.update(key,remove,false,false,WriteConcern.SAFE);
528 
529             return true;
530         }
531         else
532         {
533             return false;
534         }
535     }
536 
537     
538     
539     /** 
540      * @see org.eclipse.jetty.nosql.NoSqlSessionManager#expire(java.lang.String)
541      */
542     @Override
543     protected void expire (String idInCluster)
544     {
545         __log.debug("MongoSessionManager:expire session {} ", idInCluster);
546 
547         //Expire the session for this context
548         super.expire(idInCluster);
549         
550         //If the outer session document has not already been marked invalid, do so.
551         DBObject validKey = new BasicDBObject(__VALID, true);       
552         DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,idInCluster), validKey);
553         
554         if (o != null && (Boolean)o.get(__VALID))
555         {
556             BasicDBObject update = new BasicDBObject();
557             BasicDBObject sets = new BasicDBObject();
558             sets.put(__VALID,false);
559             sets.put(__INVALIDATED, System.currentTimeMillis());
560             update.put("$set",sets);
561                         
562             BasicDBObject key = new BasicDBObject(__ID,idInCluster);
563             _dbSessions.update(key,update,false,false,WriteConcern.SAFE);
564         }       
565     }
566     
567     
568     /*------------------------------------------------------------ */
569     /** 
570      * Change the session id. Note that this will change the session id for all contexts for which the session id is in use.
571      * @see org.eclipse.jetty.nosql.NoSqlSessionManager#update(org.eclipse.jetty.nosql.NoSqlSession, java.lang.String, java.lang.String)
572      */
573     @Override
574     protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception
575     {
576         BasicDBObject key = new BasicDBObject(__ID, session.getClusterId());
577         BasicDBObject sets = new BasicDBObject();
578         BasicDBObject update = new BasicDBObject(__ID, newClusterId);
579         sets.put("$set", update);
580         _dbSessions.update(key, sets, false, false,WriteConcern.SAFE);
581     }
582 
583     /*------------------------------------------------------------ */
584     protected String encodeName(String name)
585     {
586         return name.replace("%","%25").replace(".","%2E");
587     }
588 
589     /*------------------------------------------------------------ */
590     protected String decodeName(String name)
591     {
592         return name.replace("%2E",".").replace("%25","%");
593     }
594 
595     /*------------------------------------------------------------ */
596     protected Object encodeName(Object value) throws IOException
597     {
598         if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date)
599         {
600             return value;
601         }
602         else if (value.getClass().equals(HashMap.class))
603         {
604             BasicDBObject o = new BasicDBObject();
605             for (Map.Entry<?, ?> entry : ((Map<?, ?>)value).entrySet())
606             {
607                 if (!(entry.getKey() instanceof String))
608                 {
609                     o = null;
610                     break;
611                 }
612                 o.append(encodeName(entry.getKey().toString()),encodeName(entry.getValue()));
613             }
614 
615             if (o != null)
616                 return o;
617         }
618         
619         ByteArrayOutputStream bout = new ByteArrayOutputStream();
620         ObjectOutputStream out = new ObjectOutputStream(bout);
621         out.reset();
622         out.writeUnshared(value);
623         out.flush();
624         return bout.toByteArray();
625     }
626 
627     /*------------------------------------------------------------ */
628     protected Object decodeValue(final Object valueToDecode) throws IOException, ClassNotFoundException
629     {
630         if (valueToDecode == null || valueToDecode instanceof Number || valueToDecode instanceof String || valueToDecode instanceof Boolean || valueToDecode instanceof Date)
631         {
632             return valueToDecode;
633         }
634         else if (valueToDecode instanceof byte[])
635         {
636             final byte[] decodeObject = (byte[])valueToDecode;
637             final ByteArrayInputStream bais = new ByteArrayInputStream(decodeObject);
638             final ClassLoadingObjectInputStream objectInputStream = new ClassLoadingObjectInputStream(bais);
639             return objectInputStream.readUnshared();
640         }
641         else if (valueToDecode instanceof DBObject)
642         {
643             Map<String, Object> map = new HashMap<String, Object>();
644             for (String name : ((DBObject)valueToDecode).keySet())
645             {
646                 String attr = decodeName(name);
647                 map.put(attr,decodeValue(((DBObject)valueToDecode).get(name)));
648             }
649             return map;
650         }
651         else
652         {
653             throw new IllegalStateException(valueToDecode.getClass().toString());
654         }
655     }
656 
657    
658     /*------------------------------------------------------------ */
659     private String getContextKey()
660     {
661         return __CONTEXT + "." + _contextId;
662     }
663     
664     /*------------------------------------------------------------ */
665     /** Get a dot separated key for 
666      * @param key
667      * @return
668      */
669     private String getContextAttributeKey(String attr)
670     {
671         return getContextKey()+ "." + attr;
672     }
673     
674     /*------------------------------------------------------------ */
675     @ManagedOperation(value="purge invalid sessions in the session store based on normal criteria", impact="ACTION")
676     public void purge()
677     {   
678         ((MongoSessionIdManager)_sessionIdManager).purge();
679     }
680     
681     
682     /*------------------------------------------------------------ */
683     @ManagedOperation(value="full purge of invalid sessions in the session store", impact="ACTION")
684     public void purgeFully()
685     {   
686         ((MongoSessionIdManager)_sessionIdManager).purgeFully();
687     }
688     
689     /*------------------------------------------------------------ */
690     @ManagedOperation(value="scavenge sessions known to this manager", impact="ACTION")
691     public void scavenge()
692     {
693         ((MongoSessionIdManager)_sessionIdManager).scavenge();
694     }
695     
696     /*------------------------------------------------------------ */
697     @ManagedOperation(value="scanvenge all sessions", impact="ACTION")
698     public void scavengeFully()
699     {
700         ((MongoSessionIdManager)_sessionIdManager).scavengeFully();
701     }
702     
703     /*------------------------------------------------------------ */
704     /**
705      * returns the total number of session objects in the session store
706      * 
707      * the count() operation itself is optimized to perform on the server side
708      * and avoid loading to client side.
709      */
710     @ManagedAttribute("total number of known sessions in the store")
711     public long getSessionStoreCount()
712     {
713         return _dbSessions.find().count();      
714     }
715     
716     /*------------------------------------------------------------ */
717     /**
718      * MongoDB keys are . delimited for nesting so .'s are protected characters
719      * 
720      * @param virtualHosts
721      * @param contextPath
722      * @return
723      */
724     private String createContextId(String[] virtualHosts, String contextPath)
725     {
726         String contextId = virtualHosts[0] + contextPath;
727         
728         contextId.replace('/', '_');
729         contextId.replace('.','_');
730         contextId.replace('\\','_');
731         
732         return contextId;
733     }
734 
735     /*------------------------------------------------------------ */
736     /**
737      * Dig through a given dbObject for the nested value
738      */
739     private Object getNestedValue(DBObject dbObject, String nestedKey)
740     {
741         String[] keyChain = nestedKey.split("\\.");
742 
743         DBObject temp = dbObject;
744 
745         for (int i = 0; i < keyChain.length - 1; ++i)
746         {
747             temp = (DBObject)temp.get(keyChain[i]);
748             
749             if ( temp == null )
750             {
751                 return null;
752             }
753         }
754 
755         return temp.get(keyChain[keyChain.length - 1]);
756     }
757 
758     
759     /*------------------------------------------------------------ */
760      /**
761      * ClassLoadingObjectInputStream
762      *
763      *
764      */
765     protected class ClassLoadingObjectInputStream extends ObjectInputStream
766     {
767         public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
768         {
769             super(in);
770         }
771 
772         public ClassLoadingObjectInputStream () throws IOException
773         {
774             super();
775         }
776 
777         @Override
778         public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
779         {
780             try
781             {
782                 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
783             }
784             catch (ClassNotFoundException e)
785             {
786                 return super.resolveClass(cl);
787             }
788         }
789     }
790 
791 
792 }