View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.javaflow;
18  
19  import org.apache.commons.javaflow.bytecode.transformation.ResourceTransformer;
20  import org.apache.commons.javaflow.bytecode.transformation.bcel.BcelClassTransformer;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  
24  import java.io.ByteArrayOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.net.URL;
28  import java.net.URLClassLoader;
29  import java.security.AccessControlContext;
30  import java.security.AccessController;
31  import java.security.PrivilegedAction;
32  import java.security.ProtectionDomain;
33  import java.util.ArrayList;
34  import java.util.Iterator;
35  import java.util.List;
36  
37  /**
38   * {@link URLClassLoader} with bytecode instrumentation for javaflow.
39   *
40   * <p>
41   * This class loader is useful where the application can set up multiple
42   * class loaders (such as in a container environment,
43   * <a href="http://classworlds.codehaus.org/">ClassWorlds</a>, or
44   * <a href="http://forehead.werken.com/">Forehead</a>) and when you can
45   * isolate the continuation-enabled portion of your application into a separate
46   * jar file.
47   */
48  public final class ContinuationClassLoader extends URLClassLoader {
49  
50      private final static Log log = LogFactory.getLog(ContinuationClassLoader.class);
51  
52      private final ResourceTransformer transformer;
53  
54      /**
55       * Indicates whether the parent class loader should be
56       * consulted before trying to load with this class loader.
57       */
58      private boolean parentFirst = true;
59  
60      /**
61       * These are the package roots that are to be loaded by the parent class
62       * loader regardless of whether the parent class loader is being searched
63       * first or not.
64       */
65      private List systemPackages = new ArrayList();
66  
67      /**
68       * These are the package roots that are to be loaded by this class loader
69       * regardless of whether the parent class loader is being searched first
70       * or not.
71       */
72      private List loaderPackages = new ArrayList();
73  
74      /**
75       * Whether or not this classloader will ignore the base
76       * classloader if it can't find a class.
77       *
78       * @see #setIsolated(boolean)
79       */
80      private boolean ignoreBase = false;
81  
82      /* The context to be used when loading classes and resources */
83      private final AccessControlContext acc;
84  
85      private static final int BUFFER_SIZE = 4096;
86  
87      /**
88       * Creates a classloader by using the classpath given.
89       *
90       * @param urls
91       *      The URLs from which to load classes and resources
92       * @param parent
93       *      The parent classloader to which unsatisfied loading
94       *      attempts are delegated. May be <code>null</code>,
95       *      in which case the {@link ClassLoader#getSystemClassLoader() system classloader}
96       *      is used as the parent.
97       * @param transformer
98       *      This transformer is used to perform the byte-code enhancement.
99       *      May not be null.
100      */
101     public ContinuationClassLoader(URL[] urls, ClassLoader parent, ResourceTransformer transformer) {
102         super(urls,fixNullParent(parent));
103         if(transformer==null)
104             throw new IllegalArgumentException();
105         this.transformer = transformer;
106         acc = AccessController.getContext();
107     }
108 
109     public ContinuationClassLoader(URL[] urls, ClassLoader parent) {
110         this(urls,parent,new BcelClassTransformer());
111     }
112 
113     private static ClassLoader fixNullParent(ClassLoader classLoader) {
114         if(classLoader!=null) {
115             return classLoader;
116         } else {
117             return getSystemClassLoader();
118         }
119     }
120 
121     /**
122      * Control whether class lookup is delegated to the parent loader first
123      * or after this loader. Use with extreme caution. Setting this to
124      * false violates the class loader hierarchy and can lead to Linkage errors
125      *
126      * @param parentFirst if true, delegate initial class search to the parent
127      *                    classloader.
128      */
129     public void setParentFirst(boolean parentFirst) {
130         this.parentFirst = parentFirst;
131     }
132 
133     /**
134      * Sets whether this classloader should run in isolated mode. In
135      * isolated mode, classes not found on the given classpath will
136      * not be referred to the parent class loader but will cause a
137      * ClassNotFoundException.
138      *
139      * @param isolated Whether or not this classloader should run in
140      *                 isolated mode.
141      */
142     public void setIsolated(boolean isolated) {
143         ignoreBase = isolated;
144     }
145 
146     /**
147      * Adds a package root to the list of packages which must be loaded on the
148      * parent loader.
149      *
150      * All subpackages are also included.
151      *
152      * @param packageRoot The root of all packages to be included.
153      *                    Should not be <code>null</code>.
154      */
155     public synchronized void addSystemPackageRoot(String packageRoot) {
156         systemPackages.add(appendDot(packageRoot));
157     }
158 
159     /**
160      * Adds a package root to the list of packages which must be loaded using
161      * this loader.
162      *
163      * All subpackages are also included.
164      *
165      * @param packageRoot The root of all packages to be included.
166      *                    Should not be <code>null</code>.
167      */
168     public synchronized void addLoaderPackageRoot(String packageRoot) {
169         loaderPackages.add(appendDot(packageRoot));
170     }
171 
172     private String appendDot(String str) {
173         if(str.endsWith("."))
174             str += '.';
175         return str;
176     }
177 
178     /**
179      * Loads a class through this class loader even if that class is available
180      * on the parent classpath.
181      *
182      * This ensures that any classes which are loaded by the returned class
183      * will use this classloader.
184      *
185      * @param classname The name of the class to be loaded.
186      *                  Must not be <code>null</code>.
187      *
188      * @return the required Class object
189      *
190      * @exception ClassNotFoundException if the requested class does not exist
191      *                                   on this loader's classpath.
192      */
193     public Class forceLoadClass(String classname)
194          throws ClassNotFoundException {
195         log.debug("force loading " + classname);
196 
197         Class theClass = findLoadedClass(classname);
198 
199         if (theClass == null) {
200             theClass = findClass(classname);
201         }
202 
203         return theClass;
204     }
205 
206     /**
207      * Tests whether or not the parent classloader should be checked for
208      * a resource before this one. If the resource matches both the
209      * "use parent classloader first" and the "use this classloader first"
210      * lists, the latter takes priority.
211      *
212      * @param resourceName The name of the resource to check.
213      *                     Must not be <code>null</code>.
214      *
215      * @return whether or not the parent classloader should be checked for a
216      *         resource before this one is.
217      */
218     private synchronized boolean isParentFirst(String resourceName) {
219         // default to the global setting and then see
220         // if this class belongs to a package which has been
221         // designated to use a specific loader first
222         // (this one or the parent one)
223 
224         // XXX - shouldn't this always return false in isolated mode?
225 
226         boolean useParentFirst = parentFirst;
227 
228         for (Iterator itr = systemPackages.iterator(); itr.hasNext();) {
229             String packageName = (String) itr.next();
230             if (resourceName.startsWith(packageName)) {
231                 useParentFirst = true;
232                 break;
233             }
234         }
235 
236         for (Iterator itr = loaderPackages.iterator(); itr.hasNext();) {
237             String packageName = (String) itr.next();
238             if (resourceName.startsWith(packageName)) {
239                 useParentFirst = false;
240                 break;
241             }
242         }
243 
244         return useParentFirst;
245     }
246 
247     /**
248      * Loads a class with this class loader.
249      *
250      * This class attempts to load the class in an order determined by whether
251      * or not the class matches the system/loader package lists, with the
252      * loader package list taking priority. If the classloader is in isolated
253      * mode, failure to load the class in this loader will result in a
254      * ClassNotFoundException.
255      *
256      * @param classname The name of the class to be loaded.
257      *                  Must not be <code>null</code>.
258      * @param resolve <code>true</code> if all classes upon which this class
259      *                depends are to be loaded.
260      *
261      * @return the required Class object
262      *
263      * @exception ClassNotFoundException if the requested class does not exist
264      * on the system classpath (when not in isolated mode) or this loader's
265      * classpath.
266      */
267     protected synchronized Class loadClass(String classname, boolean resolve)
268          throws ClassNotFoundException {
269         // 'sync' is needed - otherwise 2 threads can load the same class
270         // twice, resulting in LinkageError: duplicated class definition.
271         // findLoadedClass avoids that, but without sync it won't work.
272 
273         Class theClass = findLoadedClass(classname);
274         if (theClass != null) {
275             return theClass;
276         }
277 
278         if (isParentFirst(classname)) {
279             try {
280                 theClass = getParent().loadClass(classname);
281                 log.debug("Class " + classname + " loaded from parent loader "
282                     + "(parentFirst)");
283             } catch (ClassNotFoundException cnfe) {
284                 theClass = findClass(classname);
285                 log.debug("Class " + classname + " loaded from ant loader "
286                     + "(parentFirst)");
287             }
288         } else {
289             try {
290                 theClass = findClass(classname);
291                 log.debug("Class " + classname + " loaded from ant loader");
292             } catch (ClassNotFoundException cnfe) {
293                 if (ignoreBase) {
294                     throw cnfe;
295                 }
296                 theClass = getParent().loadClass(classname);
297                 log.debug("Class " + classname + " loaded from parent loader");
298             }
299         }
300 
301         if (resolve) {
302             resolveClass(theClass);
303         }
304 
305         return theClass;
306     }
307 
308     /**
309      * Define a class given its bytes
310      *
311      * @param classData the bytecode data for the class
312      * @param classname the name of the class
313      *
314      * @return the Class instance created from the given data
315      */
316     protected Class defineClassFromData(final byte[] classData, final String classname) {
317         return (Class)AccessController.doPrivileged(new PrivilegedAction() {
318             public Object run() {
319                 // define a package if necessary.
320                 int i = classname.lastIndexOf('.');
321                 if (i>0) {
322                     String packageName = classname.substring(0,i);
323                     Package pkg = getPackage(packageName);
324                     if(pkg==null) {
325                         definePackage(packageName,null,null,null,null,null,null,null);
326                     }
327                 }
328 
329                 byte[] newData = transformer.transform(classData);
330                 ProtectionDomain domain = this.getClass().getProtectionDomain();
331                 return defineClass(classname,newData,0,newData.length,domain);
332             }
333         },acc);
334     }
335 
336     /**
337      * Reads a class definition from a stream.
338      *
339      * @param stream The stream from which the class is to be read.
340      *               Must not be <code>null</code>.
341      * @param classname The name of the class in the stream.
342      *                  Must not be <code>null</code>.
343      *
344      * @return the Class object read from the stream.
345      *
346      * @exception IOException if there is a problem reading the class from the
347      * stream.
348      * @exception SecurityException if there is a security problem while
349      * reading the class from the stream.
350      */
351     private Class getClassFromStream(InputStream stream, String classname)
352                 throws IOException, SecurityException {
353         ByteArrayOutputStream baos = new ByteArrayOutputStream();
354         int bytesRead;
355         byte[] buffer = new byte[BUFFER_SIZE];
356 
357         while ((bytesRead = stream.read(buffer, 0, BUFFER_SIZE)) != -1) {
358             baos.write(buffer, 0, bytesRead);
359         }
360 
361         byte[] classData = baos.toByteArray();
362         return defineClassFromData(classData, classname);
363     }
364 
365     /**
366      * Searches for and load a class on the classpath of this class loader.
367      *
368      * @param name The name of the class to be loaded. Must not be
369      *             <code>null</code>.
370      *
371      * @return the required Class object
372      *
373      * @exception ClassNotFoundException if the requested class does not exist
374      *                                   on this loader's classpath.
375      */
376     public Class findClass(final String name) throws ClassNotFoundException {
377         log.debug("Finding class " + name);
378 
379         // locate the class file
380         String classFileName = name.replace('.', '/') + ".class";
381 
382         InputStream stream = getResourceAsStream(classFileName);
383         if(stream==null)
384             throw new ClassNotFoundException(name);
385 
386         try {
387             return getClassFromStream(stream, name);
388         } catch (IOException e) {
389             throw new ClassNotFoundException(name,e);
390         } finally {
391             try {
392                 stream.close();
393             } catch (IOException e) {
394                 // ignore
395             }
396         }
397     }
398 
399     /**
400      * Finds the resource with the given name. A resource is
401      * some data (images, audio, text, etc) that can be accessed by class
402      * code in a way that is independent of the location of the code.
403      *
404      * @param name The name of the resource for which a stream is required.
405      *             Must not be <code>null</code>.
406      * @return a URL for reading the resource, or <code>null</code> if the
407      *         resource could not be found or the caller doesn't have
408      *         adequate privileges to get the resource.
409      */
410     public synchronized URL getResource(String name) {
411         // we need to search the components of the path to see if
412         // we can find the class we want.
413         if (isParentFirst(name)) {
414             return super.getResource(name);
415         }
416 
417         // try this class loader first, then parent
418         URL url = findResource(name);
419         if(url==null) {
420             url = getParent().getResource(name);
421         }
422         return url;
423     }
424 }