View Javadoc

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