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 }