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.
MyClassLoader(URL[] urls, ClassLoader parent, String annotation)63     MyClassLoader (URL[] urls, ClassLoader parent, String annotation)
64     {
65       super (urls, parent);
66       this.annotation = annotation;
67     }
68 
urlToAnnotation(URL[] urls)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 
getClassAnnotation()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 
CacheKey(String theCodebase, ClassLoader theContextClassLoader)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      */
equals(Object theOther)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      */
equals(Object theOne, Object theOther)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      */
hashCode()137     public int hashCode()
138     {
139       return ((mCodeBase != null           ? mCodeBase.hashCode()           :  0)
140               ^(mContextClassLoader != null ? mContextClassLoader.hashCode() : -1));
141     }
142 
toString()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
cacheLoaders.put(new CacheKey (defaultAnnotation, Thread.currentThread().getContextClassLoader()), defaultClassLoader)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    */
RMIClassLoaderImpl()197   private RMIClassLoaderImpl()
198   {
199   }
200 
201   /**
202    * Returns an instance of RMIClassLoaderImpl.
203    *
204    * @return an instance of RMIClassLoaderImpl
205    */
getInstance()206   public static RMIClassLoaderSpi getInstance()
207   {
208     if (instance == null)
209       instance = new RMIClassLoaderImpl();
210     return instance;
211   }
212 
loadClass(String codeBase, String name, ClassLoader defaultLoader)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 
loadProxyClass(String codeBase, String[] interfaces, ClassLoader defaultLoader)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    */
getClassLoader(String codebase)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    */
getClassAnnotation(Class cl)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 }
358