com.dragome.commons.DragomeInstrumentationClassLoader Maven / Gradle / Ivy
Show all versions of dragome-js-commons Show documentation
/*
* Copyright (c) 2011-2014 Fernando Petrola
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.dragome.commons;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.dragome.commons.compiler.BytecodeTransformer;
/**
* {@link URLClassLoader} with bytecode instrumentation for javaflow.
*
*
* This class loader is useful where the application can set up multiple
* class loaders (such as in a container environment,
* ClassWorlds, or
* Forehead) and when you can
* isolate the continuation-enabled portion of your application into a separate
* jar file.
*/
public final class DragomeInstrumentationClassLoader extends URLClassLoader
{
private static Logger LOGGER= Logger.getLogger(DragomeInstrumentationClassLoader.class.getName());
/**
* Indicates whether the parent class loader should be
* consulted before trying to load with this class loader.
*/
private boolean parentFirst= true;
/**
* These are the package roots that are to be loaded by the parent class
* loader regardless of whether the parent class loader is being searched
* first or not.
*/
private List systemPackages= new ArrayList();
/**
* These are the package roots that are to be loaded by this class loader
* regardless of whether the parent class loader is being searched first
* or not.
*/
private List loaderPackages= new ArrayList();
/**
* Whether or not this classloader will ignore the base
* classloader if it can't find a class.
*
* @see #setIsolated(boolean)
*/
private boolean ignoreBase= false;
/* The context to be used when loading classes and resources */
private final AccessControlContext acc;
private static final int BUFFER_SIZE= 4096;
protected Set loadFromParent= new HashSet();
private Map bytecodes= new HashMap();
private List loadingClass= new ArrayList();
private ClassLoader last;
private BytecodeTransformer bytecodeTransformer;
/**
* Creates a classloader by using the classpath given.
*
* @param urls
* The URLs from which to load classes and resources
* @param parent
* The parent classloader to which unsatisfied loading
* attempts are delegated. May be null
,
* in which case the {@link ClassLoader#getSystemClassLoader() system classloader}
* is used as the parent.
*/
public DragomeInstrumentationClassLoader(URL[] urls, ClassLoader parent)
{
super(urls, fixNullParent(parent));
acc= AccessController.getContext();
}
public DragomeInstrumentationClassLoader(URL[] urls, ClassLoader parent, ClassLoader last, BytecodeTransformer bytecodeTransformer, Set loadFromParentList)
{
this(urls, parent);
this.loadFromParent= loadFromParentList;
this.last= last;
this.bytecodeTransformer= bytecodeTransformer;
}
private static ClassLoader fixNullParent(ClassLoader classLoader)
{
if (classLoader != null)
{
return classLoader;
}
else
{
return getSystemClassLoader();
}
}
/**
* Control whether class lookup is delegated to the parent loader first
* or after this loader. Use with extreme caution. Setting this to
* false violates the class loader hierarchy and can lead to Linkage errors
*
* @param parentFirst if true, delegate initial class search to the parent
* classloader.
*/
public void setParentFirst(boolean parentFirst)
{
this.parentFirst= parentFirst;
}
/**
* Sets whether this classloader should run in isolated mode. In
* isolated mode, classes not found on the given classpath will
* not be referred to the parent class loader but will cause a
* ClassNotFoundException.
*
* @param isolated Whether or not this classloader should run in
* isolated mode.
*/
public void setIsolated(boolean isolated)
{
ignoreBase= isolated;
}
/**
* Adds a package root to the list of packages which must be loaded on the
* parent loader.
*
* All subpackages are also included.
*
* @param packageRoot The root of all packages to be included.
* Should not be null
.
*/
public synchronized void addSystemPackageRoot(String packageRoot)
{
systemPackages.add(appendDot(packageRoot));
}
/**
* Adds a package root to the list of packages which must be loaded using
* this loader.
*
* All subpackages are also included.
*
* @param packageRoot The root of all packages to be included.
* Should not be null
.
*/
public synchronized void addLoaderPackageRoot(String packageRoot)
{
loaderPackages.add(/*appendDot(*/packageRoot/*)*/);
}
public synchronized void addLoadFromParent(String packageRoot)
{
loadFromParent.add(packageRoot);
}
private String appendDot(String str)
{
if (str.endsWith("."))
str+= '.';
return str;
}
/**
* Loads a class through this class loader even if that class is available
* on the parent classpath.
*
* This ensures that any classes which are loaded by the returned class
* will use this classloader.
*
* @param classname The name of the class to be loaded.
* Must not be null
.
*
* @return the required Class object
*
* @exception ClassNotFoundException if the requested class does not exist
* on this loader's classpath.
*/
public Class forceLoadClass(String classname) throws ClassNotFoundException
{
LOGGER.finest("force loading " + classname);
Class theClass= findLoadedClass(classname);
if (theClass == null)
{
theClass= findClass(classname);
}
return theClass;
}
/**
* Tests whether or not the parent classloader should be checked for
* a resource before this one. If the resource matches both the
* "use parent classloader first" and the "use this classloader first"
* lists, the latter takes priority.
*
* @param resourceName The name of the resource to check.
* Must not be null
.
*
* @return whether or not the parent classloader should be checked for a
* resource before this one is.
*/
private synchronized boolean isParentFirst(String resourceName)
{
// default to the global setting and then see
// if this class belongs to a package which has been
// designated to use a specific loader first
// (this one or the parent one)
// XXX - shouldn't this always return false in isolated mode?
boolean useParentFirst= parentFirst;
for (Iterator itr= systemPackages.iterator(); itr.hasNext();)
{
String packageName= (String) itr.next();
if (resourceName.startsWith(packageName))
{
useParentFirst= true;
break;
}
}
for (Iterator itr= loaderPackages.iterator(); itr.hasNext();)
{
String packageName= (String) itr.next();
if (resourceName.startsWith(packageName))
{
useParentFirst= false;
break;
}
}
return useParentFirst;
}
/**
* Loads a class with this class loader.
*
* This class attempts to load the class in an order determined by whether
* or not the class matches the system/loader package lists, with the
* loader package list taking priority. If the classloader is in isolated
* mode, failure to load the class in this loader will result in a
* ClassNotFoundException.
*
* @param classname The name of the class to be loaded.
* Must not be null
.
* @param resolve true
if all classes upon which this class
* depends are to be loaded.
*
* @return the required Class object
*
* @exception ClassNotFoundException if the requested class does not exist
* on the system classpath (when not in isolated mode) or this loader's
* classpath.
*/
protected synchronized Class loadClass(String classname, boolean resolve) throws ClassNotFoundException
{
try
{
// 'sync' is needed - otherwise 2 threads can load the same class
// twice, resulting in LinkageError: duplicated class definition.
// findLoadedClass avoids that, but without sync it won't work.
Class theClass= findLoadedClass(classname);
byte[] bs= bytecodes.get(classname);
if (bs != null)
theClass= defineClassFromData(bs, classname);
if (theClass != null)
{
return theClass;
}
if (classNeedsLoadingFromParent(classname))
{
try
{
theClass= getParent().loadClass(classname);
LOGGER.finest("Class " + classname + " loaded from parent loader " + "(parentFirst)");
}
catch (ClassNotFoundException cnfe)
{
theClass= findClass(classname);
LOGGER.finest("Class " + classname + " loaded from ant loader " + "(parentFirst)");
}
}
else
{
try
{
theClass= findClass(classname);
LOGGER.finest("Class " + classname + " loaded from ant loader");
}
catch (ClassNotFoundException cnfe)
{
if (ignoreBase)
{
throw cnfe;
}
theClass= getParent().loadClass(classname);
LOGGER.finest("Class " + classname + " loaded from parent loader");
}
}
if (resolve)
{
resolveClass(theClass);
}
return theClass;
}
finally
{
loadingClass.remove(classname);
}
}
private boolean classNeedsLoadingFromParent(String classname)
{
for (String packageName : loadFromParent)
{
if (classname.startsWith((String) packageName))
return true;
}
return classname.startsWith("javassist.") || classname.startsWith("junit.") || classname.startsWith("sun.") || classname.startsWith("java.") || classname.startsWith("javax.") || classname.startsWith("org.w3c") || classname.startsWith("org.xml") || loadFromParent.contains(classname) || classname.startsWith("org.apache") /* && isParentFirst(classname) */;
}
//
// private boolean classNeedsTransformation(String classname)
// {
// for (Object packageName : loaderPackages)
// {
// if (classname.startsWith((String) packageName))
// return true;
// }
//
// return false;
// }
/**
* Define a class given its bytes
*
* @param classData the bytecode data for the class
* @param classname the name of the class
*
* @return the Class instance created from the given data
*/
public Class defineClassFromData(final byte[] classData, final String classname)
{
return (Class) AccessController.doPrivileged(new PrivilegedAction()
{
public Object run()
{
// define a package if necessary.
int i= classname.lastIndexOf('.');
if (i > 0)
{
String packageName= classname.substring(0, i);
Package pkg= getPackage(packageName);
if (pkg == null)
{
definePackage(packageName, null, null, null, null, null, null, null);
}
}
byte[] newData;
if (bytecodeTransformer == null || !bytecodeTransformer.requiresTransformation(classname) || loadingClass.contains(classname))
newData= classData;
else
{
loadingClass.add(classname);
Thread.currentThread().setContextClassLoader(last);
newData= bytecodeTransformer.transform(classname, classData);
Thread.currentThread().setContextClassLoader(DragomeInstrumentationClassLoader.this);
}
ProtectionDomain domain= this.getClass().getProtectionDomain();
return defineClass(classname, newData, 0, newData.length, domain);
}
}, acc);
}
/**
* Reads a class definition from a stream.
*
* @param stream The stream from which the class is to be read.
* Must not be null
.
* @param classname The name of the class in the stream.
* Must not be null
.
*
* @return the Class object read from the stream.
*
* @exception IOException if there is a problem reading the class from the
* stream.
* @exception SecurityException if there is a security problem while
* reading the class from the stream.
*/
private Class getClassFromStream(InputStream stream, String classname) throws IOException, SecurityException
{
ByteArrayOutputStream baos= new ByteArrayOutputStream();
int bytesRead;
byte[] buffer= new byte[BUFFER_SIZE];
while ((bytesRead= stream.read(buffer, 0, BUFFER_SIZE)) != -1)
{
baos.write(buffer, 0, bytesRead);
}
byte[] classData= baos.toByteArray();
return defineClassFromData(classData, classname);
}
/**
* Searches for and load a class on the classpath of this class loader.
*
* @param name The name of the class to be loaded. Must not be
* null
.
*
* @return the required Class object
*
* @exception ClassNotFoundException if the requested class does not exist
* on this loader's classpath.
*/
public Class findClass(final String name) throws ClassNotFoundException
{
LOGGER.finest("Finding class " + name);
// locate the class file
String classFileName= name.replace('.', '/') + ".class";
InputStream stream= getResourceAsStream(classFileName);
if (stream == null)
throw new ClassNotFoundException(name);
try
{
return getClassFromStream(stream, name);
}
catch (IOException e)
{
throw new ClassNotFoundException(name, e);
}
finally
{
try
{
stream.close();
}
catch (IOException e)
{
// ignore
}
}
}
/**
* Finds the resource with the given name. A resource is
* some data (images, audio, text, etc) that can be accessed by class
* code in a way that is independent of the location of the code.
*
* @param name The name of the resource for which a stream is required.
* Must not be null
.
* @return a URL for reading the resource, or null
if the
* resource could not be found or the caller doesn't have
* adequate privileges to get the resource.
*/
public synchronized URL getResource(String name)
{
// we need to search the components of the path to see if
// we can find the class we want.
if (isParentFirst(name))
{
return super.getResource(name);
}
// try this class loader first, then parent
URL url= findResource(name);
if (url == null)
{
url= getParent().getResource(name);
}
return url;
}
public void addLoaderPackagesRoot(String[] packagesroot)
{
for (String packageroot : packagesroot)
addLoaderPackageRoot(packageroot);
}
public void addClassBytecode(byte[] bytecode, String aName)
{
bytecodes.put(aName, bytecode);
}
public void setBytecodes(Map bytecodes)
{
this.bytecodes= bytecodes;
}
}