blob: a3c8b9f781b8b3e344e11a3002c562082012b176 [file] [log] [blame]
/*
* 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 org.apache.river.container.classloading;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.security.Security;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileUtil;
import org.apache.river.container.LocalizedRuntimeException;
import org.apache.river.container.MessageNames;
/**
*
* @author trasukg
*/
public class VirtualFileSystemClassLoader extends URLClassLoader {
private FileObject fileSystemRoot = null;
private List<ClasspathEntry> classpathEntries = new ArrayList<ClasspathEntry>();
private CodeSource codeSource = null;
private boolean isAppPriority = false;
public VirtualFileSystemClassLoader(FileObject fileSystemRoot, ClassLoader parent, CodeSource codeSource) {
this(fileSystemRoot, parent, codeSource, false);
}
public VirtualFileSystemClassLoader(FileObject fileSystemRoot, ClassLoader parent, CodeSource codeSource, boolean isAppPriority) {
super(new URL[0], parent);
this.isAppPriority = isAppPriority;
this.fileSystemRoot = fileSystemRoot;
this.codeSource = codeSource;
}
public static String classToResourceName(String name) {
String resourceName = name.replace(Strings.DOT, Strings.SLASH).concat(Strings.DOT_CLASS);
return resourceName;
}
/**
* Add the given classpath to this classloader, based on the default root
* supplied at construction time.
*
* @param classPath
*/
public void addClassPathEntry(String classPath) {
addClassPathEntry(fileSystemRoot, classPath);
}
/**
* Add the given classpath to this classloader, based on the given fileRoot.
* The classpath can contain multiple entries, separated by colons, e.g.
* 'jsk-platform.jar:jsk-lib.jar'.<br> Each entry can either be a jar file
* or a jar file with a list of classes that the jar file can be used to
* provide. For instance, 'surrogate.jar(org.apache.ABC, org.apache.DEF)'.
*
* @param fileRoot
* @param classPath
*/
public void addClassPathEntry(FileObject fileRoot, String classPath) {
try {
/*
Classpath entry is a jar file with filter expressions that can be
understood by ClasspathFilterBuilder.
*/
/*
Create a nested file system from it and add it to the file objects.
*/
List<ClasspathFilter> filters = new ClasspathFilterBuilder().parseToFilters(classPath);
addClasspathFilters(filters, fileRoot);
} catch (FileSystemException ex) {
throw new LocalizedRuntimeException(ex, MessageNames.BUNDLE_NAME, MessageNames.INVALID_CLASSPATH_ENTRY, classPath);
}
}
public void addClasspathFilters(List<ClasspathFilter> filters, FileObject fileRoot) throws FileSystemException {
for (ClasspathFilter filter : filters) {
FileObject entryObject = fileRoot.resolveFile(filter.getJarName());
FileObject entryFileSystem
= fileRoot.getFileSystem().getFileSystemManager().createFileSystem(entryObject);
classpathEntries.add(new ClasspathEntry(filter, entryFileSystem));
}
}
/**
* Find a resource by searching through all the classpath entries that have
* been set up.
*
* @param name
* @return
*/
@Override
public URL findResource(final String name) {
try {
return (URL) Security.doPrivileged(new PrivilegedExceptionAction<URL>() {
@Override
public URL run() throws Exception {
FileObject fo = findResourceFileObject(name);
return fo == null ? null : fo.getURL();
}
});
} catch (Exception ex) {
Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
@Override
public Enumeration<URL> findResources(final String name) throws IOException {
Enumeration result = (Enumeration) Security.doPrivileged(new PrivilegedAction<Enumeration>() {
public Enumeration run() {
List<URL> urlList = new ArrayList<URL>();
try {
List<FileObject> foList = findResourceFileObjects(name);
for (FileObject fo : foList) {
urlList.add(fo.getURL());
}
} catch (FileSystemException ex) {
Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
}
return Collections.enumeration(urlList);
}
});
return result;
}
/**
* Find the file object for a resource by searching through all the
* classpath entries that have been set up.
*
* @param name
* @return
*/
public FileObject findResourceFileObject(String name) {
for (ClasspathEntry cpEntry : classpathEntries) {
try {
FileObject fo = cpEntry.resolveFile(name);
if (fo != null && fo.isReadable()) {
return fo;
}
} catch (FileSystemException ex) {
Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
}
}
return null;
}
/**
* Find the all the file objects for a resource by searching through all the
* classpath entries that have been set up.
*
* @param name
* @return
*/
public List<FileObject> findResourceFileObjects(String name) {
List<FileObject> foList = new ArrayList<FileObject>();
for (ClasspathEntry cpEntry : classpathEntries) {
try {
FileObject fo = cpEntry.resolveFile(name);
if (fo != null && fo.isReadable()) {
foList.add(fo);
}
} catch (FileSystemException ex) {
Logger.getLogger(VirtualFileSystemClassLoader.class.getName()).log(Level.SEVERE, null, ex);
}
}
return foList;
}
@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
try {
return (Class) Security.doPrivileged(new PrivilegedExceptionAction<Class>() {
public Class run() throws ClassNotFoundException {
String resourceName = classToResourceName(name);
FileObject resourceFileObject = findResourceFileObject(resourceName);
if (resourceFileObject == null) {
throw new ClassNotFoundException(name + "(" + resourceName + ")");
}
try {
byte[] bytes = FileUtil.getContent(resourceFileObject);
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException ioe) {
throw new ClassNotFoundException(name, ioe);
}
}
});
} catch (PrivilegedActionException ex) {
throw (ClassNotFoundException) ex.getException();
}
}
/**
* Set the codebase URLs to an arbitrary list of URLs. These URLs form the
* codebase annotation for classes loaded through this classloader. For the
* sake of general paranoia, sets the codebase to a copy of the provided
* array.
*
* @param codebase
*/
public void setCodebase(URL[] codebase) {
if (codebase == null || codebase.length == 0) {
codebaseURLs = new URL[]{};
return;
}
codebaseURLs = new URL[codebase.length];
System.arraycopy(codebase, 0, codebaseURLs, 0, codebase.length);
}
/**
* Get the list of URLs that are used for the codebase annotation. Note that
* this list is not the actual classpath (that is in the superclass). The
* codebase URLs are imposed to match whatever the Jini service wants to
* expose as its codebase annotation.
*
* @return
*/
@Override
public URL[] getURLs() {
return codebaseURLs;
}
/**
* Stores the codebase that will be returned as the codebase annotation.
*
*/
private URL codebaseURLs[] = new URL[0];
@Override
public String toString() {
StringBuffer listString = new StringBuffer();
listString.append(format(classpathEntries));
listString.append(", codebase [");
URL[] urlArray = getURLs();
for (int i = 0; i < urlArray.length; i++) {
listString.append(" ");
listString.append(urlArray[i]);
}
listString.append("]");
return listString.toString();
}
public static String format(List<ClasspathEntry> items) {
if (items == null) {
return "null";
}
StringBuffer sb = new StringBuffer();
sb.append("[");
boolean first = true;
for (Object o : items) {
if (!first) {
sb.append(", ");
} else {
first = false;
}
sb.append("'");
sb.append(o.toString());
sb.append("'");
}
sb.append("]");
return sb.toString();
}
/**
* Loads a class with the specified name.
*
* <p>
* <code>VirtualFileSystemClassLoader</code> implements this method as
* follows:
*
* <li>If <code>isAppPriority</code> is <code>true</code>, then this method
* invokes {@link #findClass
* findClass} with <code>name</code>. If <code>findClass</code> throws an
* exception, then this method throws that exception. Otherwise, this method
* returns the <code>Class</code> returned by <code>findClass</code>, and if
* <code>resolve</code> is <code>true</code>,
* {@link #resolveClass resolveClass} is invoked with the <code>Class</code>
* before returning.
*
* <li>If <code>isAppPriority</code> is <code>false</code>, then this method
* invokes the superclass implementation of {@link ClassLoader#loadClass(String,boolean)
* loadClass} with <code>name</code> and <code>resolve</code> and returns
* the result. If the superclass's <code>loadClass</code> throws an
* exception, then this method throws that exception.
*
* </ul>
*
* @param name the binary name of the class to load
*
* @param resolve if <code>true</code>, then {@link #resolveClass
* resolveClass} will be invoked with the loaded class before returning
*
* @return the loaded class
*
* @throws ClassNotFoundException if the class could not be found
*
*/
protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c!=null) return c;
if (isAppPriority) {
try {
c = findClass(name);
} catch (ClassNotFoundException e) {
return super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(c);
}
return c;
} else {
return super.loadClass(name, resolve);
}
}
/**
* Gets a resource with the specified name.
*
* <p>
* <code>VirtualFileSystemClassLoader</code> implements this method as
* follows:
* <li>If <code>isAppPriority</code> is <code>true</code>, then this method
* invokes {@link
* #findResource findResource} with <code>name</code> and returns the
* result.
*
* <li>If <code>isAppPriority</code> is <code>false</code>, then this method
* invokes the superclass implementation of
* {@link ClassLoader#getResource getResource} with <code>name</code> and
* returns the result.
*
* </ul>
*
* @param name the name of the resource to get
*
* @return a <code>URL</code> for the resource, or <code>null</code> if the
* resource could not be found
*
*/
public URL getResource(String name) {
return isAppPriority
? findResource(name) : super.getResource(name);
}
}