All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vii.brillien.ignition.classloading.PresenceClassLoader Maven / Gradle / Ivy

/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.ignition.classloading;

import com.vii.streamline.services.IOServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.net.*;
import java.security.*;
import java.security.cert.Certificate;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

/**
 * Custom classloader to load all Presences published to the Brillien.
 */
public class PresenceClassLoader extends URLClassLoader {

    /**
     * logger for this class
     */
    static Logger                           _logger = LoggerFactory.getLogger(PresenceClassLoader.class);

    /**
     * list of url entries of this class loader
     */
    private List                  urlSet = Collections.synchronizedList(new ArrayList());

    /**
     * cache of not found resources
     */
    private Map                             notFoundResources = new HashMap();

    /**
     * cache of not found classes
     */
    private Map                             notFoundClasses = new HashMap();

    /**
     * state flag to track whether this instance has been shut off.
     */
    private boolean                         doneCalled = false;

    /**
     * snapshot of classloader state at the time done was called
     */
    private String                          doneSnapshot;

    /**
     * streams opened by this loader
     */
    private Vector     streams = null;

    private ArrayList transformers =
            new ArrayList(1);


    private PresenceClassLoader             parentPresenceClassLoader;
    private List       childrenPresenceClassLoaders;


    {
        childrenPresenceClassLoaders = Collections.synchronizedList(
                new LinkedList()
        );
    }

    /**
     * Constructor.
     */
    public PresenceClassLoader() {
        super(new URL[0]);

        _logger.debug("ClassLoader: " + this + " is getting created.");
    }

    /**
     * Constructor.
     *
     * @param parent parent class loader
     */
    public PresenceClassLoader(ClassLoader parent) {
        super(new URL[0], parent);
    }

    public boolean isDone() {
        return doneCalled;
    }

    /**
     * This method should be called to free up the resources.
     * It helps garbage collection.
     */
    public void done() {

        if (doneCalled) {
            return;
        }

        if( parentPresenceClassLoader != null )
                parentPresenceClassLoader.removeChildPresenceClassLoader( this );

        // Capture the fact that the classloader is now effectively disabled.
        // First create a snapshot of our state.  This should be called
        // before setting doneCalled = true.
        doneSnapshot = "PresenceClassLoader.done() called ON " + this.toString()
                + "\n AT " + new Date();
        doneCalled = true;

        // closes the jar handles and sets the url entries to null
        int i = 0;
        while (i < this.urlSet.size()) {
            URLEntry u = (URLEntry) this.urlSet.get(i);
            if (u.zip != null) {
                try {
                    u.zip.reallyClose();
                } catch (IOException ioe) {
                    _logger.info("URLEntry closing problem at: " + u.source, ioe);
                }
            }
            if (u.table != null) {
                u.table.clear();
                u.table = null;
            }
            u = null;
            i++;
        }

        closeOpenStreams();

        // clears out the tables
        if (this.urlSet != null) {
            this.urlSet.clear();
        }
        if (this.notFoundResources != null) {
            this.notFoundResources.clear();
        }
        if (this.notFoundClasses != null) {
            this.notFoundClasses.clear();
        }

        // sets all the objects to null
        this.urlSet = null;
        this.notFoundResources = null;
        this.notFoundClasses = null;
    }


    /**
     * Adds a URL to the search list, based on the specified File.
     * 

* This variant of the method makes sure that the URL is valid, in particular * encoding special characters (such as blanks) in the file path. * * @param file the File to use in creating the URL * @throws IOException in case of errors converting the file to a URL */ public synchronized void innerAppendURL(File file) throws IOException { try { appendURL(file.toURI().toURL()); } catch (MalformedURLException mue) { _logger.error("loader.PresenceClassLoader_bad_url_entry", file.toURI()); _logger.error("loader.PresenceClassLoader_malformed_url", mue); IOException ioe = new IOException(); ioe.initCause(mue); throw ioe; } } public synchronized void appendURL(File file) throws IOException { innerAppendURL(file); LinkedList files = new LinkedList(); IOServices.collectFiles( file, files, new FileFilter() { public boolean accept(File file) { return !file.isDirectory() && file.getName().endsWith(".jar"); } } ); for (File f : files) innerAppendURL(f); } public PresenceClassLoader setParentPresenceClassLoader(PresenceClassLoader parentPresenceClassLoader) { this.parentPresenceClassLoader = parentPresenceClassLoader; return this; } public synchronized void addChildPresenceClassLoader(PresenceClassLoader loader) { childrenPresenceClassLoaders.add(loader.setParentPresenceClassLoader(this)); } public synchronized void removeChildPresenceClassLoader(PresenceClassLoader loader) { childrenPresenceClassLoaders.remove(loader.setParentPresenceClassLoader(null)); } /** * Appends the specified URL to the list of URLs to search for * classes and resources. * * @param url the URL to be added to the search path of URLs */ public void addURL(URL url) { appendURL(url); } /** * Add a url to the list of urls we search for a class's bytecodes. * * @param url url to be added */ public synchronized void appendURL(URL url) { try { if (url == null) { _logger.debug("loader.PresenceClassLoader_bad_url_entry", url); return; } URLEntry entry = new URLEntry(url); if (!urlSet.contains(entry)) { entry.init(); // adds the url entry to the list this.urlSet.add(entry); if (entry.isJar) { // checks the manifest if a jar checkManifest(entry.zip, entry.file); } } else { _logger.debug("[B-CL] Ignoring duplicate URL: " + url); /* *Clean up the unused entry or it could hold open a jar file. */ if (entry.zip != null) { try { entry.zip.reallyClose(); } catch (IOException ioe) { _logger.debug("URLEntry closing problem at: " + entry.source, ioe); } } } // clears the "not found" cache since we are adding a new url clearNotFoundCaches(); } catch (IOException ioe) { _logger.error("loader.PresenceClassLoader_bad_url_entry", url); _logger.error("loader.PresenceClassLoader_malformed_url", ioe); } } /** * Returns the urls of this class loader. * * @return the urls of this class loader or an empty array */ public synchronized URL[] getURLs() { URL[] url = null; if (this.urlSet != null) { url = new URL[this.urlSet.size()]; for (int i = 0; i < url.length; i++) { url[i] = ((URLEntry) this.urlSet.get(i)).source; } } else { url = new URL[0]; } return url; } /** * Returns all the "file" protocol resources of this PresenceClassLoader, * concatenated to a classpath string. *

* Notice that this method is called by the setClassPath() method of * org.apache.catalina.loader.WebappLoader, since this PresenceClassLoader does * not addExtension off of URLClassLoader. * * @return Classpath string containing all the "file" protocol resources * of this PresenceClassLoader */ public String getClasspath() { StringBuffer strBuf = null; URL[] urls = getURLs(); if (urls != null) { for (int i = 0; i < urls.length; i++) { if (urls[i].getProtocol().equals("file")) { if (strBuf == null) { strBuf = new StringBuffer(); } if (i > 0) { strBuf.append(File.pathSeparator); } strBuf.append(urls[i].getFile()); } } } return (strBuf != null) ? strBuf.toString() : null; } /** * Refreshes the memory of the class loader. This involves clearing the * not-found cahces and recreating the hash tables for the URLEntries that * record the files accessible for each. *

* Code that creates an PresenceClassLoader and then adds files to a directory * that is in the loader's classpath should invoke this method after the new * file(s) have been added in order to update the class loader's data * structures which optimize class and resource searches. * * @throws IOException in case of errors refreshing the cache */ public synchronized void refresh() throws IOException { clearNotFoundCaches(); // for (URLEntry entry : urlSet) { // entry.cacheItems(); // } } public synchronized void addTransformer(ClassFileTransformer transformer) { transformers.add(transformer); } /** * Create a new instance of a sibling classloader * * @return a new instance of a class loader that has the same visibility * as this class loader */ public ClassLoader copy() { return new DelegatingClassLoader(this); } /** * Erases the memory of classes and resources that have been searched for * but not found. */ private void clearNotFoundCaches() { this.notFoundResources.clear(); this.notFoundClasses.clear(); } /** * Internal implementation of find resource. * * @param res url resource entry * @param name name of the resource */ private URL findResource0(final URLEntry res, final String name) { Object result = AccessController.doPrivileged(new PrivilegedAction() { public Object run() { if (res.isJar) { try { JarEntry jarEntry = res.zip.getJarEntry(name); if (jarEntry != null) { /* *Use a custom URL with a special stream handler to *prevent the JDK's JarURLConnection caching from *locking the jar file until JVM exit. */ InternalURLStreamHandler handler = new InternalURLStreamHandler(res, name); URI uri = new URI("jar", res.source + "!/" + name, null /* fragment */); URL ret = new URL(uri.toURL(), "" /* spec */, handler); handler.tieUrl(ret); return ret; } } catch (Throwable thr) { _logger.info("loader.excep_in_PresenceClassLoader", thr); } } else { // directory try { File resourceFile = new File(res.file.getCanonicalPath() + File.separator + name); if (resourceFile.exists()) { // If we make it this far, // the resource is in the directory. return resourceFile.toURI().toURL(); } } catch (IOException e) { _logger.info("loader.excep_in_PresenceClassLoader", e); } } return null; } // End for -- each URL in classpath. }); return (URL) result; } public URL findResource(String name) { if (doneCalled) { _logger.warn( "FindResources already ddone" + name, this.toString(), new Throwable() ); return null; } // resource is in the not found list String nf = (String) notFoundResources.get(name); if (nf != null && nf.equals(name)) { return null; } int i = 0; while (i < this.urlSet.size()) { URLEntry u = (URLEntry) this.urlSet.get(i); if (!u.hasItem(name)) { i++; continue; } URL url = findResource0(u, name); if (url != null) return url; i++; } // add resource to the not found list notFoundResources.put(name, name); return null; } /** * Returns an enumeration of java.net.URL objects * representing all the resources with the given name. */ public Enumeration findResources(String name) throws IOException { if (doneCalled) { _logger.warn( "loader.PresenceClassLoader_done_already_called", new Object[]{name, doneSnapshot} ); return null; } List resourcesList = new ArrayList(); // resource is in the not found list String nf = (String) notFoundResources.get(name); if (nf != null && nf.equals(name)) { return (new Vector(resourcesList)).elements(); } for (Iterator iter = this.urlSet.iterator(); iter.hasNext();) { URLEntry urlEntry = (URLEntry) iter.next(); URL url = findResource0(urlEntry, name); if (url != null) { resourcesList.add(url); } } if (resourcesList.size() == 0) { // add resource to the not found list notFoundResources.put(name, name); } return (new Vector(resourcesList)).elements(); } /** * Checks the manifest of the given jar file. * * @param jar the jar file that may contain manifest class path * @param file file pointer to the jar * @throws IOException if an i/o error */ private void checkManifest(JarFile jar, File file) throws IOException { if ((jar == null) || (file == null)) return; Manifest man = jar.getManifest(); if (man == null) return; synchronized (this) { String cp = man.getMainAttributes().getValue( Attributes.Name.CLASS_PATH); if (cp == null) return; StringTokenizer st = new StringTokenizer(cp, " "); while (st.hasMoreTokens()) { String entry = st.nextToken(); File newFile = new File(file.getParentFile(), entry); // add to class path of this class loader try { appendURL(newFile); } catch (MalformedURLException ex) { _logger.error("loader.PresenceClassLoader_malformed_url", ex); } } } } /** * Internal implementation of load class. * * @param res url resource entry * @param entryName name of the class */ private byte[] loadClassData0(final URLEntry res, final String entryName) { Object result = AccessController.doPrivileged(new PrivilegedAction() { public Object run() { InputStream classStream = null; try { if (res.isJar) { // It is a jarfile.. JarFile zip = res.zip; JarEntry entry = zip.getJarEntry(entryName); if (entry != null) { classStream = zip.getInputStream(entry); byte[] classData = getClassData(classStream); res.setProtectionDomain(PresenceClassLoader.this, entry.getCertificates()); return classData; } } else { // Its a directory.... File classFile = new File(res.file, entryName.replace('/', File.separatorChar)); if (classFile.exists()) { try { classStream = new FileInputStream(classFile); byte[] classData = getClassData(classStream); res.setProtectionDomain(PresenceClassLoader.this, null); return classData; } finally { /* *Close the stream only if this is a directory. The stream for *a jar/zip file was opened elsewhere and should remain open after this *method completes. */ if (classStream != null) { try { classStream.close(); } catch (IOException closeIOE) { _logger.info("loader.excep_in_PresenceClassLoader", closeIOE); } } } } } } catch (IOException ioe) { _logger.info("loader.excep_in_PresenceClassLoader", ioe); } return null; } }); return (byte[]) result; } protected Class findClass(String name) throws ClassNotFoundException { ClassData classData = findClassData(name); // Instruments the classes if the profiler's enabled // Define package information if necessary int lastPackageSep = name.lastIndexOf('.'); if (lastPackageSep != -1) { String packageName = name.substring(0, lastPackageSep); if (getPackage(packageName) == null) { try { // There's a small chance that one of our parents // could define the same package after getPackage // returns null but before we call definePackage, // since the parent classloader instances // are not locked. So, just catch the exception // that is thrown in that case and ignore it. // // It's unclear where we would get the info to // set all spec and impl data for the package, // so just use null. This is consistent will the // JDK code that does the same. definePackage(packageName, null, null, null, null, null, null, null); } catch (IllegalArgumentException iae) { // duplicate attempt to define same package. // safe to ignore. _logger.debug("duplicate package definition attempt for " + packageName, iae); } } } // Loop though the transformers here!! try { ArrayList xformers = (ArrayList) transformers.clone(); for (ClassFileTransformer transformer : xformers) { // see javadocs of transform(). // It expects class name as java/lang/Object // as opposed to java.lang.Object String internalClassName = name.replace('.', '/'); byte[] transformedBytes = transformer.transform(this, internalClassName, null, classData.pd, classData.classBytes); if (transformedBytes != null) { // null indicates no transformation _logger.info("PresenceClassLoader:findClass", "{0} actually got transformed", name); classData.classBytes = transformedBytes; } } } catch (IllegalClassFormatException icfEx) { throw new ClassNotFoundException(icfEx.toString(), icfEx); } Class clazz = null; try { clazz = defineClass(name, classData.classBytes, 0, classData.classBytes.length, classData.pd); return clazz; } catch (UnsupportedClassVersionError ucve) { throw new UnsupportedClassVersionError( "PresenceClassLoader.unsupportedVersion:" + name + " " + System.getProperty("java.version") ); } } /** * This method is responsible for locating the url from the class bytes * have to be read and reading the bytes. It does not actually define * the Class object. * * @param name class name in java.lang.Object format * @return class bytes as well protection domain information * @throws ClassNotFoundException */ protected ClassData findClassData(String name) throws ClassNotFoundException { if (doneCalled) { _logger.warn( "Findclass already done" + name, new Throwable() ); throw new ClassNotFoundException(name); } String nf = (String) notFoundClasses.get(name); if (nf != null && nf.equals(name)) { throw new ClassNotFoundException(name); } // search thru the JARs for a file of the form java/lang/Object.class String entryName = name.replace('.', '/') + ".class"; int i = 0; while (i < urlSet.size()) { URLEntry u = (URLEntry) this.urlSet.get(i); if (!u.hasItem(entryName)) { i++; continue; } byte[] result = loadClassData0(u, entryName); if (result != null) return new ClassData(result, u.pd); i++; } // add to the not found classes list notFoundClasses.put(name, name); throw new ClassNotFoundException(name); } /** * Returns the byte array from the given input stream. * * @param istream input stream to the class or resource * @throws IOException if an i/o error */ private byte[] getClassData(InputStream istream) throws IOException { BufferedInputStream bstream = new BufferedInputStream(istream); ; byte[] buf = new byte[4096]; ByteArrayOutputStream bout = new ByteArrayOutputStream(); int num = 0; try { while ((num = bstream.read(buf)) != -1) { bout.write(buf, 0, num); } } finally { if (bstream != null) { try { bstream.close(); } catch (IOException closeIOE) { PresenceClassLoader._logger.info("loader.excep_in_PresenceClassLoader", closeIOE); } } } return bout.toByteArray(); } /** * Returns a string representation of this class loader. * * @return a string representation of this class loader */ public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("PresenceClassLoader : \n"); if (doneCalled) { buffer.append("doneCalled = true" + "\n"); if (doneSnapshot != null) { buffer.append("doneSnapshot = " + doneSnapshot); } } else { buffer.append("urlSet = " + this.urlSet + "\n"); buffer.append("doneCalled = false " + "\n"); } buffer.append(" Parent -> " + getParent() + "\n"); return buffer.toString(); } public InputStream getResourceAsStream(final String name) { InputStream stream = super.getResourceAsStream(name); /* *Make sure not to wrap the stream if it already is a wrapper. */ if (stream != null) { if (!(stream instanceof SentinelInputStream)) { stream = new SentinelInputStream(stream); } } return stream; } protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { try{ return super.loadClass( name, resolve ); } catch( ClassNotFoundException cnfe ){ for( PresenceClassLoader childLoader : childrenPresenceClassLoaders ){ try{ Class c = childLoader.cascadeLoadClass( name, resolve ); if( c != null ) return c; } catch( ClassNotFoundException ccnfe ){ } } throw cnfe; } } private Class cascadeLoadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = null; c = findLoadedClass(name); if (c == null) { try{ c = findClass(name); } catch(ClassNotFoundException e){ for( PresenceClassLoader childLoader : childrenPresenceClassLoaders ){ try{ c = childLoader.cascadeLoadClass( name, resolve ); if( c != null ) return c; } catch( ClassNotFoundException ccnfe ){ } } throw e; } } if (resolve) { resolveClass(c); } return c; } /** * The JarFile objects loaded in the classloader may get exposed to the * application code (e.g. EJBs) through calls of * ((JarURLConnection) getResource().openConnection()).getJarFile(). *

* This class protects the jar file from being closed by such an application. * * @author fkieviet */ private static class ProtectedJarFile extends JarFile { /** * Constructor * * @param file File * @throws IOException from parent */ public ProtectedJarFile(File file) throws IOException { super(file); } /** * Do nothing * * @see java.util.zip.ZipFile#close() */ public void close() { // nothing _logger.warn("Illegal call to close() detected", new Throwable()); } /** * Really close the jar file * * @throws IOException from parent */ public void reallyClose() throws IOException { super.close(); } /** * @see java.lang.Object#finalize() */ protected void finalize() throws IOException { reallyClose(); } } /** * URL entry - keeps track of the url resources. */ protected static class URLEntry { /** * the url */ URL source = null; /** * file of the url */ File file = null; /** * jar file if url is a jar else null */ ProtectedJarFile zip = null; /** * true if url is a jar */ boolean isJar = false; Hashtable table = null; /** * ProtectionDomain with signers if jar is signed */ ProtectionDomain pd = null; URLEntry(URL url) { source = url; } void init() throws IOException { try { file = new File(source.toURI()); isJar = file.isFile(); if (isJar) { zip = new ProtectedJarFile(file); } table = new Hashtable(); // cacheItems(); } catch (URISyntaxException use) { IOException ioe = new IOException(); ioe.initCause(use); throw ioe; } } private void cacheItems() throws IOException { if (isJar) { // cache entry names from jar file for (Enumeration e = zip.entries(); e.hasMoreElements();) { ZipEntry curEntry = (ZipEntry) e.nextElement(); table.put(curEntry.getName(), curEntry.getName()); } } else { // cache entry names from directory if (file.exists()) { fillTable(file, table, ""); } } } private void fillTable(File f, Hashtable t, String parent) throws IOException { String localName = (parent.equals("")) ? "" : parent + "/"; File[] children = f.listFiles(); for (int i = 0; i < children.length; i++) { processFile(children[i], t, localName); } } /** * Adds a file (or, if a directory, the directory's contents) to the table * of files this loader knows about. *

* Invokes fillTable for subdirectories which in turn invokes processFile * recursively. * * @param fileToProcess the File to be processed * @param t the Hashtable that holds the files the loader knows about * @param parentLocalName prefix to be used for the full path; should be * non-empty only for recursive invocations * @throws IOException in case of errors working with the fileToProcess */ private void processFile(File fileToProcess, Hashtable t, String parentLocalName) throws IOException { String key = parentLocalName + fileToProcess.getName(); if (fileToProcess.isFile()) { t.put(key, key); } else if (fileToProcess.isDirectory()) { fillTable(fileToProcess, t, key); } } boolean hasItem(String item) { // in the case of ejbc stub compilation, PresenceClassLoader is created before stubs // gets generated, thus we need to return true for this case. if (table.size() == 0) { return true; } /* *Even with the previous special handling, a file could be created *in a directory after the loader was created and its table of *URLEntry names populated. So check the table first and, if *the target item is not there and this URLEntry is for a directory, look for *the file. If the file is now present but was not when the loader *was created, add an entry for the file in the table. */ boolean result = false; String target = item; // special handling if (item.startsWith("./")) { target = item.substring(2, item.length()); } result = table.containsKey(target); if (!result && !isJar) { /* *If the file exists now then it has been added to the directory since the *loader was created. Add it to the table of files we *know about. */ File targetFile = privilegedCheckForFile(target); if (targetFile != null) { try { processFile(targetFile, table, ""); result = true; } catch (IOException ioe) { _logger.error("Error processing file:" + target + " " + file.getAbsolutePath(), ioe); return false; } } } return result; } /** * Returns a File object for the requested path within the URLEntry. *

* Runs privileged because user code could trigger invocations of this * method. * * @param targetPath the relative path to look for * @return File object for the requested file; null if it does not exist or * in case of error */ private File privilegedCheckForFile(final String targetPath) { /* *Check for the file existence with privs, because this code can *be invoked from user code which may not otherwise have access *to the directories of interest. */ try { File result = (File) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { File targetFile = new File(file, targetPath); if (!targetFile.exists()) { targetFile = null; } return targetFile; } }); return result; } catch (PrivilegedActionException pae) { /* *Log any exception and return false. */ _logger.error("Error checking existence:" + targetPath + " " + file.getAbsolutePath(), pae.getCause()); return null; } } /** * Sets ProtectionDomain with CodeSource including Signers in * Entry for use in call to defineClass. * * @param signers the array of signer certs or null */ public void setProtectionDomain(ClassLoader PresenceClassLoader, Certificate[] signers) throws MalformedURLException { if (pd == null) { pd = new ProtectionDomain(new CodeSource(file.toURI().toURL(), signers), null, PresenceClassLoader, null); } } public String toString() { return "URLEntry : " + source.toString(); } /** * Returns true if two URL entries has equal URLs. * * @param obj URLEntry to compare against * @return true if both entry has equal URL */ public boolean equals(Object obj) { boolean tf = false; if (obj instanceof URLEntry) { URLEntry e = (URLEntry) obj; if (source.equals(e.source)) { tf = true; } } return tf; } /** * Since equals is overridden, we need to override hashCode as well. */ public int hashCode() { return source.hashCode(); } } /** * Returns the vector of open streams; creates it if needed. * * @return Vector holding open streams */ private Vector getStreams() { if (streams == null) { streams = new Vector(); } return streams; } /** * Closes any streams that remain open, logging a warning for each. *

* This method should be invoked when the loader will no longer be used * and the app will no longer explicitly close any streams it may have opened. */ private void closeOpenStreams() { if (streams != null) { SentinelInputStream[] toClose = streams.toArray(new SentinelInputStream[streams.size()]); for (SentinelInputStream s : toClose) { try { s.closeWithWarning(); } catch (IOException ioe) { _logger.warn("loader.PresenceClassLoader_error_closing_stream", ioe); } } streams.clear(); streams = null; } } /** * Wraps all InputStreams returned by this class loader to report when * a finalizer is run before the stream has been closed. This helps * to identify where locked files could arise. * * @author vtsyganok * @author tjquinn */ protected class SentinelInputStream extends FilterInputStream { private boolean closed;// = false; private final Throwable throwable; /** * Constructs new FilteredInputStream which reports InputStreams not closed properly. * When the garbage collector runs the finalizer. If the stream is still open this class will * report a stack trace showing where the stream was opened. * * @param in - InputStream to be wrapped */ protected SentinelInputStream(final InputStream in) { super(in); throwable = new Throwable(); getStreams().add(this); } /** * Closes underlying input stream. */ public void close() throws IOException { _close(); } /** * Invoked by Garbage Collector. If underlying InputStream was not closed properly, * the stack trace of the constructor will be logged! */ protected void finalize() throws Throwable { if (!closed && this.in != null) { try { in.close(); } catch (IOException ignored) { //Cannot do anything here. } //Well, give them a stack trace! report(); } super.finalize(); } private void _close() throws IOException { closed = true; getStreams().remove(this); super.close(); } private void closeWithWarning() throws IOException { _close(); report(); } /** * Report "left-overs"! */ private void report() { _logger.warn("Input stream has been finalized or forced closed without being explicitly closed; stream instantiation reported in following stack trace", this.throwable); } } /** * To properly close streams obtained through URL.getResource().getStream(): * this opens the input stream on a JarFile that is already open as part * of the classloader, and returns a sentinel stream on it. * * @author fkieviet */ private class InternalJarURLConnection extends JarURLConnection { private URL mURL; private URLEntry mRes; private String mName; /** * Constructor * * @param url the URL that is a stream for * @param res URLEntry * @param name String * @throws MalformedURLException from super class */ public InternalJarURLConnection(URL url, URLEntry res, String name) throws MalformedURLException { super(url); mRes = res; mName = name; } /** * @see java.net.JarURLConnection#getJarFile() */ public JarFile getJarFile() throws IOException { return mRes.zip; } /** * @see java.net.URLConnection#connect() */ public void connect() throws IOException { // Nothing } /** * @see java.net.URLConnection#getInputStream() */ public InputStream getInputStream() throws IOException { ZipEntry entry = mRes.zip.getEntry(mName); return new SentinelInputStream(mRes.zip.getInputStream(entry)); } } /** * To properly close streams obtained through URL.getResource().getStream(): * an instance of this class is instantiated for each and every URL object * created by this classloader. It provides a custom JarURLConnection * (InternalJarURLConnection) so that the stream can be obtained from an already * open jar file. * * @author fkieviet */ private class InternalURLStreamHandler extends URLStreamHandler { private URL mURL; private URLEntry mRes; private String mName; /** * Constructor * * @param res URLEntry * @param name String */ public InternalURLStreamHandler(URLEntry res, String name) { mRes = res; mName = name; } /** * @see java.net.URLStreamHandler#openConnection(java.net.URL) */ protected URLConnection openConnection(URL u) throws IOException { if (u != mURL) { // ref compare on purpose // This should never happen throw new IOException("Cannot open a foreign URL; this.url=" + mURL + "; foreign.url=" + u); } return new InternalJarURLConnection(u, mRes, mName); } /** * Ties the URL that this handler is associated with to the handler, so * that it can be asserted that somehow no other URLs are mangled in (this * is theoretically impossible) * * @param url URL */ public void tieUrl(URL url) { mURL = url; } } /** * This class is used as return value of findClassIntenal method to return * both class bytes and protection domain. */ private static class ClassData { protected byte[] classBytes; protected ProtectionDomain pd; ClassData(byte[] classData, ProtectionDomain pd) { this.classBytes = classData; this.pd = pd; } } /** * This class loader only provides a new class loading namespace * so that persistence provider can load classes in that separate * namespace while scanning annotations. * This class loader delegates all stream handling (i.e. reading * actual class/resource data) operations to the application class loader. * It only defines the Class using the byte codes. * Motivation behind this class is discussed at * https://glassfish.dev.java.net/issues/show_bug.cgi?id=237. */ private static class DelegatingClassLoader extends SecureClassLoader { /** * The application class loader which is used to read class data. */ private PresenceClassLoader delegate = null; /** * Create a new instance. * * @param applicationCL is the original class loader associated * with this application. The new class loader uses it to delegate * stream handling operations. The new class loader also uses * applicationCL's parent as its own parent. */ DelegatingClassLoader(PresenceClassLoader applicationCL) { super(applicationCL.getParent()); // normal class loading delegation this.delegate = applicationCL; } /** * This method uses the delegate to use class bytes and then defines * the class using this class loader */ protected Class findClass(String name) throws ClassNotFoundException { ClassData classData = delegate.findClassData(name); // Define package information if necessary int lastPackageSep = name.lastIndexOf('.'); if (lastPackageSep != -1) { String packageName = name.substring(0, lastPackageSep); if (getPackage(packageName) == null) { try { // There's a small chance that one of our parents // could define the same package after getPackage // returns null but before we call definePackage, // since the parent classloader instances // are not locked. So, just catch the exception // that is thrown in that case and ignore it. // // It's unclear where we would get the info to // set all spec and impl data for the package, // so just use null. This is consistent will the // JDK code that does the same. definePackage(packageName, null, null, null, null, null, null, null); } catch (IllegalArgumentException iae) { // duplicate attempt to define same package. // safe to ignore. _logger.debug("duplicate package " + "definition attempt for " + packageName, iae); } } } Class clazz = null; try { clazz = defineClass(name, classData.classBytes, 0, classData.classBytes.length, classData.pd); return clazz; } catch (UnsupportedClassVersionError ucve) { throw new UnsupportedClassVersionError( "PresenceClassLoader.unsupportedVersion" + name + " " + System.getProperty("java.version") ); } } protected URL findResource(String name) { return delegate.findResource(name); } protected Enumeration findResources(String name) throws IOException { return delegate.findResources(name); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy