org.apache.river.container.classloading.VirtualFileSystemClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.river.container.classloading.VirtualFileSystemClassLoader.java

Source

/*
 * 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);
    }

}