View Javadoc

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