Coverage Report - org.apache.commons.javaflow.ContinuationClassLoader
 
Classes in this File Line Coverage Branch Coverage Complexity
ContinuationClassLoader
0%
0/96
0%
0/32
2.875
ContinuationClassLoader$1
0%
0/10
0%
0/4
2.875
 
 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  0
 public final class ContinuationClassLoader extends URLClassLoader {
 49  
 
 50  0
     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  0
     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  0
     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  0
     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  0
     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  0
         super(urls,fixNullParent(parent));
 103  0
         if(transformer==null)
 104  0
             throw new IllegalArgumentException();
 105  0
         this.transformer = transformer;
 106  0
         acc = AccessController.getContext();
 107  0
     }
 108  
 
 109  
     public ContinuationClassLoader(URL[] urls, ClassLoader parent) {
 110  0
         this(urls,parent,new BcelClassTransformer());
 111  0
     }
 112  
 
 113  
     private static ClassLoader fixNullParent(ClassLoader classLoader) {
 114  0
         if(classLoader!=null) {
 115  0
             return classLoader;
 116  
         } else {
 117  0
             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  0
         this.parentFirst = parentFirst;
 131  0
     }
 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  0
         ignoreBase = isolated;
 144  0
     }
 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  0
         systemPackages.add(appendDot(packageRoot));
 157  0
     }
 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  0
         loaderPackages.add(appendDot(packageRoot));
 170  0
     }
 171  
 
 172  
     private String appendDot(String str) {
 173  0
         if(str.endsWith("."))
 174  0
             str += '.';
 175  0
         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  0
         log.debug("force loading " + classname);
 196  
 
 197  0
         Class theClass = findLoadedClass(classname);
 198  
 
 199  0
         if (theClass == null) {
 200  0
             theClass = findClass(classname);
 201  
         }
 202  
 
 203  0
         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  0
         boolean useParentFirst = parentFirst;
 227  
 
 228  0
         for (Iterator itr = systemPackages.iterator(); itr.hasNext();) {
 229  0
             String packageName = (String) itr.next();
 230  0
             if (resourceName.startsWith(packageName)) {
 231  0
                 useParentFirst = true;
 232  0
                 break;
 233  
             }
 234  0
         }
 235  
 
 236  0
         for (Iterator itr = loaderPackages.iterator(); itr.hasNext();) {
 237  0
             String packageName = (String) itr.next();
 238  0
             if (resourceName.startsWith(packageName)) {
 239  0
                 useParentFirst = false;
 240  0
                 break;
 241  
             }
 242  0
         }
 243  
 
 244  0
         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  0
         Class theClass = findLoadedClass(classname);
 274  0
         if (theClass != null) {
 275  0
             return theClass;
 276  
         }
 277  
 
 278  0
         if (isParentFirst(classname)) {
 279  
             try {
 280  0
                 theClass = getParent().loadClass(classname);
 281  0
                 log.debug("Class " + classname + " loaded from parent loader "
 282  
                     + "(parentFirst)");
 283  0
             } catch (ClassNotFoundException cnfe) {
 284  0
                 theClass = findClass(classname);
 285  0
                 log.debug("Class " + classname + " loaded from ant loader "
 286  
                     + "(parentFirst)");
 287  0
             }
 288  0
         } else {
 289  
             try {
 290  0
                 theClass = findClass(classname);
 291  0
                 log.debug("Class " + classname + " loaded from ant loader");
 292  0
             } catch (ClassNotFoundException cnfe) {
 293  0
                 if (ignoreBase) {
 294  0
                     throw cnfe;
 295  
                 }
 296  0
                 theClass = getParent().loadClass(classname);
 297  0
                 log.debug("Class " + classname + " loaded from parent loader");
 298  0
             }
 299  
         }
 300  
 
 301  0
         if (resolve) {
 302  0
             resolveClass(theClass);
 303  
         }
 304  
 
 305  0
         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  0
         return (Class)AccessController.doPrivileged(new PrivilegedAction() {
 318  0
             public Object run() {
 319  
                 // define a package if necessary.
 320  0
                 int i = classname.lastIndexOf('.');
 321  0
                 if (i>0) {
 322  0
                     String packageName = classname.substring(0,i);
 323  0
                     Package pkg = getPackage(packageName);
 324  0
                     if(pkg==null) {
 325  0
                         definePackage(packageName,null,null,null,null,null,null,null);
 326  
                     }
 327  
                 }
 328  
 
 329  0
                 byte[] newData = transformer.transform(classData);
 330  0
                 ProtectionDomain domain = this.getClass().getProtectionDomain();
 331  0
                 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  0
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 354  
         int bytesRead;
 355  0
         byte[] buffer = new byte[BUFFER_SIZE];
 356  
 
 357  0
         while ((bytesRead = stream.read(buffer, 0, BUFFER_SIZE)) != -1) {
 358  0
             baos.write(buffer, 0, bytesRead);
 359  0
         }
 360  
 
 361  0
         byte[] classData = baos.toByteArray();
 362  0
         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  0
         log.debug("Finding class " + name);
 378  
 
 379  
         // locate the class file
 380  0
         String classFileName = name.replace('.', '/') + ".class";
 381  
 
 382  0
         InputStream stream = getResourceAsStream(classFileName);
 383  0
         if(stream==null)
 384  0
             throw new ClassNotFoundException(name);
 385  
 
 386  
         try {
 387  0
             return getClassFromStream(stream, name);
 388  0
         } catch (IOException e) {
 389  0
             throw new ClassNotFoundException(name,e);
 390  
         } finally {
 391  0
             try {
 392  0
                 stream.close();
 393  0
             } catch (IOException e) {
 394  
                 // ignore
 395  0
             }
 396  0
         }
 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  0
         if (isParentFirst(name)) {
 414  0
             return super.getResource(name);
 415  
         }
 416  
 
 417  
         // try this class loader first, then parent
 418  0
         URL url = findResource(name);
 419  0
         if(url==null) {
 420  0
             url = getParent().getResource(name);
 421  
         }
 422  0
         return url;
 423  
     }
 424  
 }