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 gnu.java.lang.CPStringBuilder;
  42: 
  43: import java.lang.reflect.Proxy;
  44: import java.net.MalformedURLException;
  45: import java.net.URL;
  46: import java.net.URLClassLoader;
  47: import java.rmi.server.RMIClassLoaderSpi;
  48: import java.util.ArrayList;
  49: import java.util.Hashtable;
  50: import java.util.Map;
  51: import java.util.StringTokenizer;
  52: 
  53: /**
  54:  * The default implementation of {@link java.rmi.server.RMIClassLoaderSpi}.
  55:  *
  56:  * @author Roman Kennke (kennke@aicas.com)
  57:  */
  58: public class RMIClassLoaderImpl extends RMIClassLoaderSpi
  59: {
  60:   private static class MyClassLoader extends URLClassLoader
  61:   {
  62:     // Package-private to avoid a trampoline constructor.
  63:     MyClassLoader (URL[] urls, ClassLoader parent, String annotation)
  64:     {
  65:       super (urls, parent);
  66:       this.annotation = annotation;
  67:     }
  68: 
  69:     public static String urlToAnnotation (URL[] urls)
  70:     {
  71:       if (urls.length == 0)
  72:         return null;
  73: 
  74:       CPStringBuilder annotation = new CPStringBuilder (64 * urls.length);
  75: 
  76:       for (int i = 0; i < urls.length; i++)
  77:       {
  78:         annotation.append (urls [i].toExternalForm());
  79:         annotation.append (' ');
  80:       }
  81: 
  82:       return annotation.toString();
  83:     }
  84: 
  85:     public final String getClassAnnotation()
  86:     {
  87:       return annotation;
  88:     }
  89: 
  90:     private final String annotation;
  91:   }
  92: 
  93:   /**
  94:    * This class is used to identify a cached classloader by its codebase and
  95:    * the context classloader that is its parent.
  96:    */
  97:   private static class CacheKey
  98:   {
  99:      private String mCodeBase;
 100:      private ClassLoader mContextClassLoader;
 101: 
 102:      public CacheKey (String theCodebase, ClassLoader theContextClassLoader)
 103:      {
 104:        mCodeBase = theCodebase;
 105:        mContextClassLoader = theContextClassLoader;
 106:      }
 107: 
 108:     /**
 109:      * @return true if the codebase and the context classloader are equal
 110:      */
 111:     public boolean equals (Object theOther)
 112:     {
 113:       if (theOther instanceof CacheKey)
 114:       {
 115:         CacheKey key = (CacheKey) theOther;
 116: 
 117:         return (equals (this.mCodeBase,key.mCodeBase)
 118:                 && equals (this.mContextClassLoader, key.mContextClassLoader));
 119:         }
 120:       return false;
 121:     }
 122: 
 123:     /**
 124:      * Test if the two objects are equal or both null.
 125:      * @param theOne
 126:      * @param theOther
 127:      * @return
 128:      */
 129:     private boolean equals (Object theOne, Object theOther)
 130:     {
 131:       return theOne != null ? theOne.equals (theOther) : theOther == null;
 132:     }
 133: 
 134:     /**
 135:      * @return hashCode
 136:      */
 137:     public int hashCode()
 138:     {
 139:       return ((mCodeBase != null           ? mCodeBase.hashCode()           :  0)
 140:               ^(mContextClassLoader != null ? mContextClassLoader.hashCode() : -1));
 141:     }
 142: 
 143:     public String toString()
 144:     {
 145:       return "[" + mCodeBase + "," + mContextClassLoader + "]";
 146:     }
 147: 
 148:   }
 149: 
 150:   private static RMIClassLoaderImpl instance = null;
 151: 
 152:   private static Map cacheLoaders; //map annotations to loaders
 153:   private static Map cacheAnnotations; //map loaders to annotations
 154:   //class loader for defaultAnnotation
 155:   private static MyClassLoader defaultClassLoader;
 156: 
 157:   //defaultAnnotation is got from system property
 158:   // "java.rmi.server.defaultAnnotation"
 159:   private static String defaultAnnotation;
 160: 
 161:   //URL object for defaultAnnotation
 162:   private static URL defaultCodebase;
 163: 
 164:   static
 165:   {
 166:     // 89 is a nice prime number for Hashtable initial capacity
 167:     cacheLoaders = new Hashtable (89);
 168:     cacheAnnotations = new Hashtable (89);
 169: 
 170:     defaultAnnotation = System.getProperty ("java.rmi.server.defaultAnnotation");
 171: 
 172:     try
 173:       {
 174:         if (defaultAnnotation != null)
 175:           defaultCodebase = new URL (defaultAnnotation);
 176:       }
 177:     catch (Exception _)
 178:       {
 179:         defaultCodebase = null;
 180:       }
 181: 
 182:     if (defaultCodebase != null)
 183:       {
 184:         defaultClassLoader = new MyClassLoader (new URL[] { defaultCodebase }, null,
 185:                                                defaultAnnotation);
 186:         // XXX using getContextClassLoader here *cannot* be right
 187:         cacheLoaders.put (new CacheKey (defaultAnnotation,
 188:                                         Thread.currentThread().getContextClassLoader()),
 189:                                         defaultClassLoader);
 190:       }
 191:     }
 192: 
 193:   /**
 194:    * This is a singleton class and may only be instantiated once from within
 195:    * the {@link #getInstance} method.
 196:    */
 197:   private RMIClassLoaderImpl()
 198:   {
 199:   }
 200: 
 201:   /**
 202:    * Returns an instance of RMIClassLoaderImpl.
 203:    *
 204:    * @return an instance of RMIClassLoaderImpl
 205:    */
 206:   public static RMIClassLoaderSpi getInstance()
 207:   {
 208:     if (instance == null)
 209:       instance = new RMIClassLoaderImpl();
 210:     return instance;
 211:   }
 212: 
 213:   public Class loadClass(String codeBase, String name,
 214:                          ClassLoader defaultLoader)
 215:     throws MalformedURLException, ClassNotFoundException
 216:   {
 217:     try
 218:       {
 219:         if (defaultLoader != null)
 220:             return Class.forName(name, false, defaultLoader);
 221:       }
 222:     catch (ClassNotFoundException e)
 223:       {
 224:       }
 225: 
 226:     return Class.forName(name, false, getClassLoader(codeBase));
 227:   }
 228: 
 229:   public Class loadProxyClass(String codeBase, String[] interfaces,
 230:                               ClassLoader defaultLoader)
 231:       throws MalformedURLException, ClassNotFoundException
 232:   {
 233:     Class clss[] = new Class[interfaces.length];
 234: 
 235:     for (int i = 0; i < interfaces.length; i++)
 236:       {
 237:         clss[i] = loadClass(codeBase, interfaces[i], defaultLoader);
 238:       }
 239: 
 240:     // Chain all class loaders (they may differ).
 241:     ArrayList loaders = new ArrayList(clss.length);
 242:     ClassLoader loader = null;
 243:     for (int i = 0; i < clss.length; i++)
 244:       {
 245:         loader = clss[i].getClassLoader();
 246:         if (! loaders.contains(loader))
 247:           {
 248:             loaders.add(0, loader);
 249:           }
 250:       }
 251:     if (loaders.size() > 1)
 252:       {
 253:         loader = new CombinedClassLoader(loaders);
 254:       }
 255: 
 256:     try
 257:       {
 258:         return Proxy.getProxyClass(loader, clss);
 259:       }
 260:     catch (IllegalArgumentException e)
 261:       {
 262:         throw new ClassNotFoundException(null, e);
 263:       }
 264:   }
 265: 
 266:   /**
 267:    * Gets a classloader for the given codebase and with the current
 268:    * context classloader as parent.
 269:    *
 270:    * @param codebase
 271:    *
 272:    * @return a classloader for the given codebase
 273:    *
 274:    * @throws MalformedURLException if the codebase contains a malformed URL
 275:    */
 276:   public ClassLoader getClassLoader(String codebase)
 277:     throws MalformedURLException
 278:   {
 279:     if (codebase == null || codebase.length() == 0)
 280:       return Thread.currentThread().getContextClassLoader();
 281: 
 282:     ClassLoader loader;
 283:     CacheKey loaderKey = new CacheKey
 284:     (codebase, Thread.currentThread().getContextClassLoader());
 285:     loader = (ClassLoader) cacheLoaders.get (loaderKey);
 286: 
 287:     if (loader == null)
 288:       {
 289:         //create an entry in cacheLoaders mapping a loader to codebases.
 290:         // codebases are separated by " "
 291:         StringTokenizer tok = new StringTokenizer (codebase, " ");
 292:         ArrayList urls = new ArrayList();
 293: 
 294:         while (tok.hasMoreTokens())
 295:           urls.add (new URL(tok.nextToken()));
 296: 
 297:         loader = new MyClassLoader((URL[]) urls.toArray(new URL [urls.size()]),
 298:                                  Thread.currentThread().getContextClassLoader(),
 299:                                  codebase);
 300:         cacheLoaders.put (loaderKey, loader);
 301:       }
 302: 
 303:     return loader;
 304:   }
 305: 
 306:   /**
 307:    * Returns a string representation of the network location where a remote
 308:    * endpoint can get the class-definition of the given class.
 309:    *
 310:    * @param cl
 311:    *
 312:    * @return a space seperated list of URLs where the class-definition
 313:    * of cl may be found
 314:    */
 315:   public String getClassAnnotation(Class cl)
 316:   {
 317:     ClassLoader loader = cl.getClassLoader();
 318: 
 319:     if (loader == null
 320:         || loader == ClassLoader.getSystemClassLoader())
 321:       {
 322:         return System.getProperty ("java.rmi.server.codebase");
 323:       }
 324: 
 325:     if (loader instanceof MyClassLoader)
 326:       {
 327:         return ((MyClassLoader) loader).getClassAnnotation();
 328:       }
 329: 
 330:     String s = (String) cacheAnnotations.get (loader);
 331: 
 332:     if (s != null)
 333:       return s;
 334: 
 335:     if (loader instanceof URLClassLoader)
 336:       {
 337:         URL[] urls = ((URLClassLoader) loader).getURLs();
 338: 
 339:         if (urls.length == 0)
 340:           return null;
 341: 
 342:         CPStringBuilder annotation = new CPStringBuilder (64 * urls.length);
 343: 
 344:         for (int i = 0; i < urls.length; i++)
 345:           {
 346:             annotation.append (urls [i].toExternalForm());
 347:             annotation.append (' ');
 348:           }
 349: 
 350:         s = annotation.toString();
 351:         cacheAnnotations.put (loader, s);
 352:         return s;
 353:       }
 354: 
 355:     return System.getProperty ("java.rmi.server.codebase");
 356:   }
 357: }