org.pepstock.jem.util.ReverseURLClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.pepstock.jem.util.ReverseURLClassLoader.java

Source

/**
JEM, the BEE - Job Entry Manager, the Batch Execution Environment
Copyright (C) 2012-2015   Andrea "Stock" Stocchero
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.pepstock.jem.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.pepstock.jem.log.LogAppl;

/**
 * Custom class loader. First of all it tries to load a class by itself, and only then delegates to parent.<br>
 * To search a class, works following this path:<br>
 * <ul>
 * <li> Bootstrap classpath
 * <li> list of the files of this class loader
 * <li> parent class loader
 * </ul>
 * 
 * @author Andrea "Stock" Stocchero
 * @version 2.2
 */
public class ReverseURLClassLoader extends URLClassLoader {

    private List<JarFile> bootstrap = new LinkedList<JarFile>();

    private boolean isParentDelegation = true;

    /**
     * Constructor with all files (passed by URL) and classvloader parent. loads all bootstrap files
     * 
     * @param urls list of files
     * @param parent class loader parent
     */
    public ReverseURLClassLoader(URL[] urls, ClassLoader parent) {
        this(urls, parent, true);
    }

    /**
     * Constructor with all files (passed by URL) and classvloader parent. loads all bootstrap files
     * 
     * @param urls list of files
     * @param parent class loader parent
     * @param isParentDelegation if it must search class to the parent or not
     */
    public ReverseURLClassLoader(URL[] urls, ClassLoader parent, boolean isParentDelegation) {
        super(urls, parent);
        // loads bootstrap files
        loadBootstrapFiles();
        this.isParentDelegation = isParentDelegation;
    }

    /* (non-Javadoc)
    * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
    */
    @Override
    protected synchronized Class<?> loadClass(String classname, boolean resolve) throws ClassNotFoundException {
        // checks if the class is already loaded
        Class<?> theClass = findLoadedClass(classname);
        if (theClass != null) {
            return theClass;
        }
        // checks if the class name is in the bootstrap classpath (like java.*)
        if (isBootStrapClass(classname)) {
            theClass = findSystemClass(classname);
        } else {
            // if not in bootstrap, searches in the list of URL passed 
            try {
                theClass = findClass(classname);
            } catch (ClassNotFoundException e) {
                if (isParentDelegation) {
                    LogAppl.getInstance().ignore(e.getMessage(), e);
                    // if not found again, goes to the parent classloader
                    theClass = getParent().loadClass(classname);
                } else {
                    throw e;
                }
            }
        }
        // if it must resolve
        if (resolve) {
            resolveClass(theClass);
        }
        return theClass;
    }

    /* (non-Javadoc)
    * @see java.lang.ClassLoader#getResource(java.lang.String)
    */
    @Override
    public URL getResource(String name) {
        // we need to search the components of the path to see if
        // we can find the class we want.
        URL url = null;

        // scans all URLS
        URL[] urls = getURLs();
        for (int i = 0; i < urls.length; i++) {
            // checks inside the files
            url = getResourceURL(urls[i], name);
            // if found, return URL
            if (url != null) {
                return url;
            }
        }
        // if not found, asks to the parent
        if (url == null) {
            url = super.getResource(name);
        }
        return url;
    }

    /* (non-Javadoc)
     * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
     */
    @Override
    public InputStream getResourceAsStream(String name) {
        // we need to search the components of the path to see if
        // we can find the class we want.
        InputStream is = null;

        // scans all URLS
        URL[] urls = getURLs();
        for (int i = 0; i < urls.length; i++) {
            // checks inside the files
            is = getResourceStream(urls[i], name);
            // if found, return input stream
            if (is != null) {
                return is;
            }
        }
        // if not found, asks to the parent
        if (is == null) {
            is = super.getResourceAsStream(name);
        }
        return is;
    }

    /**
      * Returns the URL of a given resource in the given file which may
      * either be a directory or a jar file.
      *
      * @param file The file (directory or jar) in which to search for
      *             the resource. Must not be <code>null</code>.
      * @param resourceName The name of the resource for which a stream
      *                     is required. Must not be <code>null</code>.
      *
      * @return a stream to the required resource or <code>null</code> if the
      *         resource cannot be found in the given file object.
      */
    private URL getResourceURL(URL url, String resourceName) {
        JarFile jFile = null;
        try {
            // gets the file from URL
            File file = new File(url.toURI());
            // if is a directory, then checks on the file system
            // where URL id the parent file and resource name is the file
            if (file.isDirectory()) {
                File resource = new File(file, resourceName);
                // checks if exists
                if (resource.exists()) {
                    // returns URL
                    return resource.toURI().toURL();
                }
            } else if (file.exists()) {
                // if here, the URL must be a link to a JAR file
                jFile = new JarFile(file);
                // searches in the JAR for the resource name
                JarEntry entry = jFile.getJarEntry(resourceName);
                // if found return the JAR URL
                if (entry != null) {
                    return new URL("jar:" + file.toURI().toURL() + "!/" + entry);
                }
            }
        } catch (Exception e) {
            LogAppl.getInstance().ignore(e.getMessage(), e);
        } finally {
            // closes the JAR file if open
            if (jFile != null) {
                try {
                    jFile.close();
                } catch (IOException e) {
                    LogAppl.getInstance().ignore(e.getMessage(), e);
                }
            }
        }
        return null;
    }

    /**
      * Returns an input stream to a given resource in the given file which may
      * either be a directory or a jar file.
      *
      * @param url the file (directory or jar) in which to search for the
      *             resource. Must not be <code>null</code>.
      * @param resourceName The name of the resource for which a stream is
      *                     required. Must not be <code>null</code>.
      *
      * @return a stream to the required resource or <code>null</code> if
      *         the resource cannot be found in the given file.
      */
    private InputStream getResourceStream(URL url, String resourceName) {
        JarFile jFile = null;
        try {
            // gets the file from URL
            File file = new File(url.toURI());
            // if is a directory, then checks on the file system
            // where URL id the parent file and resource name is the file
            if (file.isDirectory()) {
                File resource = new File(file, resourceName);
                // checks if exists
                if (resource.exists()) {
                    // returns inpu stream
                    return new FileInputStream(resource);
                }
            } else if (file.exists()) {
                // if here, the URL must be a link to a JAR file
                jFile = new JarFile(file);
                // searches in the JAR for the resource name
                JarEntry entry = jFile.getJarEntry(resourceName);
                // if found return the JAR InputStream
                if (entry != null) {
                    // FINDBUGS: it's correct do not close the jar file
                    // otherwise the stream will be closed
                    InputStream resourceIS = jFile.getInputStream(entry);
                    // reads input stream in byte array
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    return copy(jFile, resourceIS, baos);
                }
                // close always the jar file
                jFile.close();
            }
        } catch (Exception e) {
            LogAppl.getInstance().ignore(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Copies the resource input stream in a byte array. In this way I can close the input stream of the resource.
     * @param jFile jar file to be closed after copying
     * @param resourceIS input stream of the resource inside the jar
     * @param baos byte output stream used to copy the bytes of the resource
     * @return returns a byte array with the resource
     */
    private ByteArrayInputStream copy(JarFile jFile, InputStream resourceIS, ByteArrayOutputStream baos) {
        try {
            // copies to bytes array
            IOUtils.copy(resourceIS, baos);
            // closes jar input stream
            IOUtils.closeQuietly(resourceIS);
            IOUtils.closeQuietly(baos);
            // creates an input stream of bytes
            return new ByteArrayInputStream(baos.toByteArray());
        } catch (IOException e) {
            // ignore
            LogAppl.getInstance().ignore(e.getMessage(), e);
        } finally {
            // close always the jar file
            try {
                jFile.close();
            } catch (IOException e) {
                LogAppl.getInstance().ignore(e.getMessage(), e);
            }
        }
        return null;
    }

    /**
     * Checks if the class name passed is in the boot strap classpath (like java.* or javax.*) 
     * @param classname class name to search
     * @return <code>true</code> if the class name is defined inside of boot strap class path
     */
    private boolean isBootStrapClass(String classname) {
        // replace . with / because inside of JAR the classes are stored as in a file system
        String classNameFile = StringUtils.replace(classname, ".", "/").concat(".class");
        // scans all JAR to check if the class is inside of jars
        for (JarFile file : bootstrap) {
            if (file.getEntry(classNameFile) != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * Loads all JARS of the bootstrap classpath. If JRE/jDK doesn't support bootstrap, could create 
     * issues. Uses java.management to get these information
     */
    private void loadBootstrapFiles() {
        // 
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        // checks if bootstrap classpath is supporte
        if (runtime.isBootClassPathSupported()) {
            // gets all files
            String longFiles = ManagementFactory.getRuntimeMXBean().getBootClassPath();
            // splits by path separator
            String[] files = StringUtils.split(longFiles, File.pathSeparator);
            // reads all JAR files
            for (int i = 0; i < files.length; i++) {
                try {
                    JarFile jFile = new JarFile(files[i]);
                    bootstrap.add(jFile);
                } catch (IOException e) {
                    LogAppl.getInstance().ignore(e.getMessage(), e);
                }
            }
        }
    }
}