Source for gnu.java.rmi.server.RMIClassLoaderImpl

   1: /* RMIClassLoaderImpl.java -- FIXME: briefly describe file purpose
   2:    Copyright (C) 2005, 2006 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.java.rmi.server;
  40: 
  41: import java.lang.reflect.Proxy;
  42: import java.net.MalformedURLException;
  43: import java.net.URL;
  44: import java.net.URLClassLoader;
  45: import java.rmi.server.RMIClassLoaderSpi;
  46: import java.util.ArrayList;
  47: import java.util.Hashtable;
  48: import java.util.Map;
  49: import java.util.StringTokenizer;
  50: 
  51: /**
  52:  * The default implementation of {@link java.rmi.server.RMIClassLoaderSpi}.
  53:  *
  54:  * @author Roman Kennke (kennke@aicas.com)
  55:  */
  56: public class RMIClassLoaderImpl extends RMIClassLoaderSpi
  57: {
  58:   private static class MyClassLoader extends URLClassLoader
  59:   {
  60:     // Package-private to avoid a trampoline constructor.
  61:     MyClassLoader (URL[] urls, ClassLoader parent, String annotation)
  62:     {
  63:       super (urls, parent);
  64:       this.annotation = annotation;
  65:     }
  66: 
  67:     private MyClassLoader (URL[] urls, ClassLoader parent)
  68:     {
  69:       super (urls, parent);
  70:       this.annotation = urlToAnnotation (urls);
  71:     }
  72: 
  73:     public static String urlToAnnotation (URL[] urls)
  74:     {
  75:       if (urls.length == 0)
  76:         return null;
  77: 
  78:       StringBuffer annotation = new StringBuffer (64 * urls.length);
  79: 
  80:       for (int i = 0; i < urls.length; i++)
  81:       {
  82:         annotation.append (urls [i].toExternalForm());
  83:         annotation.append (' ');
  84:       }
  85: 
  86:       return annotation.toString();
  87:     }
  88: 
  89:     public final String getClassAnnotation()
  90:     {
  91:       return annotation;
  92:     }
  93: 
  94:     private final String annotation;
  95:   }
  96: 
  97:   /** 
  98:    * This class is used to identify a cached classloader by its codebase and 
  99:    * the context classloader that is its parent.
 100:    */  
 101:   private static class CacheKey
 102:   {
 103:      private String mCodeBase;
 104:      private ClassLoader mContextClassLoader;
 105:     
 106:      public CacheKey (String theCodebase, ClassLoader theContextClassLoader)
 107:      {
 108:        mCodeBase = theCodebase;
 109:        mContextClassLoader = theContextClassLoader;
 110:      }
 111:     
 112:     /**
 113:      * @return true if the codebase and the context classloader are equal
 114:      */
 115:     public boolean equals (Object theOther)
 116:     {
 117:       if (theOther instanceof CacheKey)
 118:       {
 119:         CacheKey key = (CacheKey) theOther;
 120:     
 121:         return (equals (this.mCodeBase,key.mCodeBase)
 122:                 && equals (this.mContextClassLoader, key.mContextClassLoader));
 123:         }
 124:       return false;
 125:     }
 126:     
 127:     /**
 128:      * Test if the two objects are equal or both null.
 129:      * @param theOne
 130:      * @param theOther
 131:      * @return
 132:      */
 133:     private boolean equals (Object theOne, Object theOther)
 134:     {
 135:       return theOne != null ? theOne.equals (theOther) : theOther == null;
 136:     }
 137: 
 138:     /**
 139:      * @return hashCode  
 140:      */
 141:     public int hashCode()
 142:     {
 143:       return ((mCodeBase != null           ? mCodeBase.hashCode()           :  0) 
 144:               ^(mContextClassLoader != null ? mContextClassLoader.hashCode() : -1));
 145:     }
 146: 
 147:     public String toString()
 148:     {
 149:       return "[" + mCodeBase + "," + mContextClassLoader + "]"; 
 150:     }
 151: 
 152:   }
 153: 
 154:   private static RMIClassLoaderImpl instance = null;
 155: 
 156:   private static Map cacheLoaders; //map annotations to loaders
 157:   private static Map cacheAnnotations; //map loaders to annotations
 158:   //class loader for defaultAnnotation
 159:   private static MyClassLoader defaultClassLoader;
 160: 
 161:   //defaultAnnotation is got from system property
 162:   // "java.rmi.server.defaultAnnotation"
 163:   private static String defaultAnnotation;
 164: 
 165:   //URL object for defaultAnnotation
 166:   private static URL defaultCodebase;
 167: 
 168:   static
 169:   {
 170:     // 89 is a nice prime number for Hashtable initial capacity
 171:     cacheLoaders = new Hashtable (89);
 172:     cacheAnnotations = new Hashtable (89);
 173: 
 174:     defaultAnnotation = System.getProperty ("java.rmi.server.defaultAnnotation");
 175: 
 176:     try
 177:       {
 178:         if (defaultAnnotation != null)
 179:           defaultCodebase = new URL (defaultAnnotation);
 180:       }
 181:     catch (Exception _)
 182:       {
 183:         defaultCodebase = null;
 184:       }
 185: 
 186:     if (defaultCodebase != null)
 187:       {
 188:         defaultClassLoader = new MyClassLoader (new URL[] { defaultCodebase }, null,
 189:                                                defaultAnnotation);
 190:         // XXX using getContextClassLoader here *cannot* be right
 191:         cacheLoaders.put (new CacheKey (defaultAnnotation,
 192:                                         Thread.currentThread().getContextClassLoader()),
 193:                                         defaultClassLoader);
 194:       }
 195:     }
 196: 
 197:   /**
 198:    * This is a singleton class and may only be instantiated once from within
 199:    * the {@link #getInstance} method.
 200:    */
 201:   private RMIClassLoaderImpl()
 202:   {
 203:   }
 204: 
 205:   /**
 206:    * Returns an instance of RMIClassLoaderImpl.
 207:    *
 208:    * @return an instance of RMIClassLoaderImpl
 209:    */
 210:   public static RMIClassLoaderSpi getInstance()
 211:   {
 212:     if (instance == null)
 213:       instance = new RMIClassLoaderImpl();
 214:     return instance;
 215:   }
 216: 
 217:   public Class loadClass(String codeBase, String name,
 218:                          ClassLoader defaultLoader)
 219:     throws MalformedURLException, ClassNotFoundException
 220:   {
 221:     try 
 222:       {
 223:         if (defaultLoader != null)
 224:             return Class.forName(name, false, defaultLoader);
 225:       }
 226:     catch (ClassNotFoundException e)
 227:       {
 228:       }
 229: 
 230:     return Class.forName(name, false, getClassLoader(codeBase));
 231:   }
 232: 
 233:   public Class loadProxyClass(String codeBase, String[] interfaces,
 234:                               ClassLoader defaultLoader)
 235:       throws MalformedURLException, ClassNotFoundException
 236:   {
 237:     Class clss[] = new Class[interfaces.length];
 238: 
 239:     for (int i = 0; i < interfaces.length; i++)
 240:       {
 241:         clss[i] = loadClass(codeBase, interfaces[i], defaultLoader);
 242:       }
 243:     
 244:     // Chain all class loaders (they may differ).
 245:     ArrayList loaders = new ArrayList(clss.length);
 246:     ClassLoader loader = null;
 247:     for (int i = 0; i < clss.length; i++)
 248:       {
 249:         loader = clss[i].getClassLoader();
 250:         if (! loaders.contains(loader))
 251:           {
 252:             loaders.add(0, loader);
 253:           }
 254:       }
 255:     if (loaders.size() > 1)
 256:       {
 257:         loader = new CombinedClassLoader(loaders);
 258:       }
 259: 
 260:     try
 261:       {
 262:         return Proxy.getProxyClass(loader, clss);
 263:       }
 264:     catch (IllegalArgumentException e)
 265:       {
 266:         throw new ClassNotFoundException(null, e);
 267:       }
 268:   }
 269: 
 270:   /**
 271:    * Gets a classloader for the given codebase and with the current
 272:    * context classloader as parent.
 273:    * 
 274:    * @param codebase
 275:    * 
 276:    * @return a classloader for the given codebase
 277:    * 
 278:    * @throws MalformedURLException if the codebase contains a malformed URL
 279:    */
 280:   public ClassLoader getClassLoader(String codebase)
 281:     throws MalformedURLException
 282:   {
 283:     if (codebase == null || codebase.length() == 0)
 284:       return Thread.currentThread().getContextClassLoader();
 285: 
 286:     ClassLoader loader;
 287:     CacheKey loaderKey = new CacheKey
 288:     (codebase, Thread.currentThread().getContextClassLoader());
 289:     loader = (ClassLoader) cacheLoaders.get (loaderKey);
 290:     
 291:     if (loader == null)
 292:       {
 293:         //create an entry in cacheLoaders mapping a loader to codebases.
 294:         // codebases are separated by " "
 295:         StringTokenizer tok = new StringTokenizer (codebase, " ");
 296:         ArrayList urls = new ArrayList();
 297:         
 298:         while (tok.hasMoreTokens())
 299:           urls.add (new URL(tok.nextToken()));
 300:         
 301:         loader = new MyClassLoader((URL[]) urls.toArray(new URL [urls.size()]),
 302:                                  Thread.currentThread().getContextClassLoader(),
 303:                                  codebase);
 304:         cacheLoaders.put (loaderKey, loader);
 305:       }
 306:     
 307:     return loader;
 308:   }
 309: 
 310:   /**
 311:    * Returns a string representation of the network location where a remote
 312:    * endpoint can get the class-definition of the given class.
 313:    *
 314:    * @param cl
 315:    *
 316:    * @return a space seperated list of URLs where the class-definition
 317:    * of cl may be found
 318:    */
 319:   public String getClassAnnotation(Class cl)
 320:   {
 321:     ClassLoader loader = cl.getClassLoader();
 322:     
 323:     if (loader == null
 324:         || loader == ClassLoader.getSystemClassLoader())
 325:       {
 326:         return System.getProperty ("java.rmi.server.codebase");
 327:       }
 328:     
 329:     if (loader instanceof MyClassLoader)
 330:       {
 331:         return ((MyClassLoader) loader).getClassAnnotation();
 332:       }
 333:     
 334:     String s = (String) cacheAnnotations.get (loader);
 335:     
 336:     if (s != null)
 337:       return s;
 338:     
 339:     if (loader instanceof URLClassLoader)
 340:       {
 341:         URL[] urls = ((URLClassLoader) loader).getURLs();
 342:         
 343:         if (urls.length == 0)
 344:           return null;
 345:         
 346:         StringBuffer annotation = new StringBuffer (64 * urls.length);
 347:         
 348:         for (int i = 0; i < urls.length; i++)
 349:           {
 350:             annotation.append (urls [i].toExternalForm());
 351:             annotation.append (' ');
 352:           }
 353:         
 354:         s = annotation.toString();
 355:         cacheAnnotations.put (loader, s);
 356:         return s;
 357:       }
 358: 
 359:     return System.getProperty ("java.rmi.server.codebase");
 360:   }
 361: }