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 }