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

com.gemstone.gemfire.internal.ClassPathLoader Maven / Gradle / Ivy

There is a newer version: 2.0-BETA
Show newest version
/*
 * Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
 *
 * 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. See accompanying
 * LICENSE file.
 */
package com.gemstone.gemfire.internal;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;

/**
 * The delegating ClassLoader used by GemFire to load classes and other resources. This ClassLoader
 * delegates to any ClassLoaders added to the list of custom class loaders, thread context ClassLoader
 * s unless they have been excluded}, the ClassLoader which loaded the GemFire classes, and finally the system
 * ClassLoader.
 * 

* The thread context class loaders can be excluded by setting the system property * gemfire.excludeThreadContextClassLoader: *

    *
  • -Dgemfire.excludeThreadContextClassLoader=true *
  • System.setProperty("gemfire.excludeThreadContextClassLoader", "true"); * *
*

* Class loading and resource loading order: *

    *
  • 1. Any custom loaders in the order they were added *
  • 2. Thread.currentThread().getContextClassLoader() unless excludeTCCL == true *
  • 3. ClassPathLoader.class.getClassLoader() *
  • 4. ClassLoader.getSystemClassLoader() If the attempt to acquire any of the above class loaders results * in either a {@link java.lang.SecurityException SecurityException} or a null, then that class loader is quietly * skipped. Duplicate class loaders will be skipped. * * @author Kirk Lund * @since 6.5.1.4 */ public final class ClassPathLoader { public static final String ENABLE_TRACE_PROPERTY = "gemfire.ClassPathLoader.enableTrace"; public static final String ENABLE_TRACE_DEFAULT_VALUE = "false"; private final boolean ENABLE_TRACE = false; public static final String EXCLUDE_TCCL_PROPERTY = "gemfire.excludeThreadContextClassLoader"; public static final boolean EXCLUDE_TCCL_DEFAULT_VALUE = false; private final boolean excludeTCCL; // This calculates the location of the extlib directory relative to the // location of the gemfire.jar file. If for some reason the ClassPathLoader // class is found in a directory instead of a JAR file (as when testing), // then it will be relative to the location of the root of the package and // class. public static final File EXT_LIB_DIR = new File((new File(ClassPathLoader.class.getProtectionDomain().getCodeSource() .getLocation().getPath())).getParent(), "ext"); // This token is placed into the list of class loaders to determine where // to insert the TCCL when in forName(...), getResource(...), etc. private static final ClassLoader TCCL_PLACEHOLDER = new ClassLoader() { // This is never used for class loading }; private static final AtomicReference latest = new AtomicReference(); private final List classLoaders; private final ClassLoaderInterface customLoaderIF; private static final Set defaultLoaders; static { defaultLoaders = new HashSet(); try { ClassLoader classLoader = ClassPathLoader.class.getClassLoader(); if (classLoader != null) { defaultLoaders.add(classLoader); } } catch (SecurityException sex) { // Nothing to do, just don't add it } try { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); if (classLoader != null) { defaultLoaders.add(classLoader); } } catch (SecurityException sex) { // Nothing to do, just don't add it } setLatestToDefault(); } /** * Starting at the files or directories identified by 'files', search for valid * JAR files and return a list of their URLs. Sub-directories will also be * searched. * * @param files Files or directories to search for valid JAR content. * * @return A list of URLs for all JAR files found. */ private static List getJarURLsFromFiles(final File... files) { final List urls = new ArrayList(); Assert.assertTrue(files != null, "file list cannot be null"); for (File file : files) { if (file.exists()) { if (file.isDirectory()) { urls.addAll(getJarURLsFromFiles(file.listFiles())); } else { if (!JarClassLoader.hasValidJarContent(file)) { warning("Invalid JAR content when attempting to create ClassLoader for file: " + file.getAbsolutePath()); continue; } try { urls.add(file.toURI().toURL()); } catch (MalformedURLException muex) { warning("Encountered invalid URL when attempting to create ClassLoader for file: " + file.getAbsolutePath() + ":" + muex.getMessage()); continue; } } } } return urls; } private ClassPathLoader(final List classLoaders, ClassLoaderInterface customLoaderIF, final boolean excludeTCCL) { Assert.assertTrue(classLoaders != null, "custom loaders must not be null"); for (ClassLoader classLoader : classLoaders) { Assert.assertTrue(classLoader != null, "null classloaders not allowed"); } this.classLoaders = new ArrayList(classLoaders); this.customLoaderIF = customLoaderIF; this.excludeTCCL = excludeTCCL; } /** * Get a copy of the collection of ClassLoaders currently in use. * * @return Collection of ClassLoaders currently in use. */ public Collection getClassLoaders() { List classLoadersCopy = new ArrayList(this.classLoaders); for (int i = 0; i < classLoadersCopy.size(); i++) { if (classLoadersCopy.get(i).equals(TCCL_PLACEHOLDER)) { if (excludeTCCL) { classLoadersCopy.remove(i); } else { classLoadersCopy.set(i, Thread.currentThread().getContextClassLoader()); } break; } } return classLoadersCopy; } // This is exposed for testing. static ClassPathLoader createWithDefaults(final boolean excludeTCCL) { List classLoaders = new LinkedList(); classLoaders.add(TCCL_PLACEHOLDER); for (final ClassLoader classLoader : defaultLoaders) { classLoaders.add(classLoader); } // Add user JAR files from the EXT_LIB_DIR directory using a single ClassLoader try { if (EXT_LIB_DIR.exists()) { if (!EXT_LIB_DIR.isDirectory() || !EXT_LIB_DIR.canRead()) { warning("Cannot read from directory when attempting to load JAR files: " + EXT_LIB_DIR.getAbsolutePath()); } else { List extLibJarURLs = getJarURLsFromFiles(EXT_LIB_DIR); ClassLoader classLoader = new URLClassLoader(extLibJarURLs.toArray(new URL[extLibJarURLs.size()])); classLoaders.add(classLoader); } } } catch (SecurityException sex) { // Nothing to do, just don't add it } return new ClassPathLoader(classLoaders, null, excludeTCCL); } public static ClassPathLoader setLatestToDefault() { return setLatestToDefault(Boolean.getBoolean(EXCLUDE_TCCL_PROPERTY)); } public static ClassPathLoader setLatestToDefault(final boolean excludeTCCL) { ClassPathLoader classPathLoader = createWithDefaults(excludeTCCL); // Clean up JarClassLoaders that attached to the previous ClassPathLoader ClassPathLoader oldClassPathLoader = latest.getAndSet(classPathLoader); if (oldClassPathLoader != null) { for (ClassLoader classLoader : oldClassPathLoader.classLoaders) { if (classLoader instanceof JarClassLoader) { ((JarClassLoader) classLoader).cleanUp(); } } } return classPathLoader; } /** * Used by GemFireXD to plugin its own class loading mechanism for deployed jars * in database and otherwise. */ public static ClassPathLoader setLatestToDefaultWithCustomLoader( final boolean excludeTCCL, ClassLoaderInterface customLoader) { List classLoaders = new LinkedList(); classLoaders.add(TCCL_PLACEHOLDER); for (final ClassLoader classLoader : defaultLoaders) { classLoaders.add(classLoader); } ClassPathLoader classPathLoader = new ClassPathLoader(classLoaders, customLoader, excludeTCCL); // Clean up JarClassLoaders that attached to the previous ClassPathLoader ClassPathLoader oldClassPathLoader = latest.getAndSet(classPathLoader); if (oldClassPathLoader != null) { for (ClassLoader classLoader : oldClassPathLoader.classLoaders) { if (classLoader instanceof JarClassLoader) { ((JarClassLoader)classLoader).cleanUp(); } } } return classPathLoader; } // This is exposed for testing. ClassPathLoader addOrReplace(final ClassLoader classLoader) { if (ENABLE_TRACE) { trace("adding classLoader: " + classLoader); } List classLoadersCopy = new ArrayList(this.classLoaders); classLoadersCopy.add(0, classLoader); // Ensure there is only one instance of this class loader in the list ClassLoader removingClassLoader = null; int index = classLoadersCopy.lastIndexOf(classLoader); if (index != 0) { removingClassLoader = classLoadersCopy.get(index); if (ENABLE_TRACE) { trace("removing previous classLoader: " + removingClassLoader); } classLoadersCopy.remove(index); } if (removingClassLoader != null && removingClassLoader instanceof JarClassLoader) { ((JarClassLoader) removingClassLoader).cleanUp(); } return new ClassPathLoader(classLoadersCopy, null, this.excludeTCCL); } /** * Add or replace the provided {@link ClassLoader} to the list held by this ClassPathLoader. Then use the resulting * list to create a new ClassPathLoader and set it as the latest. * * @param classLoader * {@link ClassLoader} to add */ public ClassPathLoader addOrReplaceAndSetLatest(final ClassLoader classLoader) { ClassPathLoader classPathLoader = addOrReplace(classLoader); latest.set(classPathLoader); return classPathLoader; } // This is exposed for testing. ClassPathLoader remove(final ClassLoader classLoader) { if (ENABLE_TRACE) { trace("removing classLoader: " + classLoader); } List classLoadersCopy = new ArrayList(); classLoadersCopy.addAll(this.classLoaders); if (!classLoadersCopy.contains(classLoader)) { if (ENABLE_TRACE) { trace("cannot remove classLoader since it doesn't exist: " + classLoader); } return this; } classLoadersCopy.remove(classLoader); if (classLoader instanceof JarClassLoader) { ((JarClassLoader) classLoader).cleanUp(); } return new ClassPathLoader(classLoadersCopy, null, this.excludeTCCL); } /** * Remove the provided {@link ClassLoader} from the list held by this ClassPathLoader. Then use the resulting list to * create a new ClassPathLoader and set it as the latest. Silently ignores requests to remove non-existent * ClassLoaders. * * @param classLoader * {@link ClassLoader} to remove */ public ClassPathLoader removeAndSetLatest(final ClassLoader classLoader) { ClassPathLoader classPathLoader = remove(classLoader); latest.set(classPathLoader); return classPathLoader; } public URL getResource(final String name) { if (ENABLE_TRACE) { trace(new StringBuilder("getResource(").append(name).append(")")); } URL url = null; ClassLoader tccl = null; if (!excludeTCCL) { tccl = Thread.currentThread().getContextClassLoader(); } for (ClassLoader classLoader : this.classLoaders) { if (classLoader == TCCL_PLACEHOLDER) { try { if (tccl != null) { if (ENABLE_TRACE) { trace(new StringBuilder("getResource trying TCCL: ").append(tccl)); } url = tccl.getResource(name); if (url != null) { if (ENABLE_TRACE) { trace("getResource found by TCCL"); } return url; } } else { if (ENABLE_TRACE) { trace(new StringBuilder("getResource skipping TCCL because it's null")); } } } catch (SecurityException sex) { // Continue to next ClassLoader } } else if (excludeTCCL || !classLoader.equals(tccl)) { if (ENABLE_TRACE) { trace(new StringBuilder("getResource trying classLoader: ").append(classLoader)); } url = classLoader.getResource(name); if (url != null) { if (ENABLE_TRACE) { trace(new StringBuilder("getResource found by classLoader: ").append(classLoader)); } return url; } } } if (ENABLE_TRACE) { trace(new StringBuilder("getResource returning null")); } return url; } public Class forName(final String name) throws ClassNotFoundException { if (ENABLE_TRACE) { trace(new StringBuilder("forName(").append(name).append(")")); } Class clazz = null; ClassLoader tccl = null; if (!excludeTCCL) { tccl = Thread.currentThread().getContextClassLoader(); } if (this.customLoaderIF != null) { if (ENABLE_TRACE) { trace(new StringBuilder("forName for class " + name + " trying customLoaderIF: ").append(this.customLoaderIF)); } try { clazz = this.customLoaderIF.loadApplicationClass(name); if (clazz != null) { if (ENABLE_TRACE) { trace(new StringBuilder("forName for class " + name + " found by customLoaderIF: ").append(this.customLoaderIF)); } return clazz; } } catch (ClassNotFoundException cnfe) { // continue to the next classloader } } for (ClassLoader classLoader : this.classLoaders) { try { if (classLoader == TCCL_PLACEHOLDER) { if (tccl != null) { if (ENABLE_TRACE) { trace(new StringBuilder("forName trying TCCL: ").append(tccl)); } clazz = Class.forName(name, true, tccl); if (clazz != null) { if (ENABLE_TRACE) { trace("forName found by TCCL"); } return clazz; } else { if (ENABLE_TRACE) { trace(new StringBuilder("forName skipping TCCL because it's null")); } } } } else if (excludeTCCL || !classLoader.equals(tccl)) { if (ENABLE_TRACE) { trace(new StringBuilder("forName trying classLoader: ").append(classLoader)); } clazz = Class.forName(name, true, classLoader); if (clazz != null) { if (ENABLE_TRACE) { trace(new StringBuilder("forName found by classLoader: ").append(classLoader)); } return clazz; } } } catch (SecurityException sex) { // Continue to next ClassLoader } catch (ClassNotFoundException cnfex) { // Continue to next ClassLoader } } if (ENABLE_TRACE) { trace(new StringBuilder("forName throwing ClassNotFoundException")); } throw new ClassNotFoundException(name); } /** * See {@link Proxy#getProxyClass(ClassLoader, Class...)} */ public Class getProxyClass(final Class[] classObjs) { IllegalArgumentException ex = null; ClassLoader tccl = null; if (!excludeTCCL) { tccl = Thread.currentThread().getContextClassLoader(); } for (ClassLoader classLoader : this.classLoaders) { try { if (classLoader == TCCL_PLACEHOLDER) { if (tccl != null) { return Proxy.getProxyClass(tccl, classObjs); } } else if (excludeTCCL || !classLoader.equals(tccl)) { return Proxy.getProxyClass(classLoader, classObjs); } } catch (SecurityException sex) { // Continue to next classloader } catch (IllegalArgumentException iaex) { ex = iaex; // Continue to next classloader } } assert ex != null; if (ex != null) { throw ex; } return null; } @Override public String toString() { final StringBuilder sb = new StringBuilder(getClass().getName()); sb.append("@").append(System.identityHashCode(this)).append("{"); sb.append("isLatest=").append(getLatest() == this); sb.append(", excludeTCCL=").append(this.excludeTCCL); if (this.customLoaderIF != null) { sb.append(", customLoaderIF=").append(this.customLoaderIF); } sb.append(", classLoaders=["); for (int i = 0; i < this.classLoaders.size(); i++) { if (i > 0) { sb.append(", "); } sb.append(this.classLoaders.get(i).toString()); } sb.append("]"); if (!this.excludeTCCL) { sb.append(", TCCL=").append(Thread.currentThread().getContextClassLoader()); } sb.append("]}"); return sb.toString(); } /** * Finds the resource with the given name. This method will first search the class loader of the context class for the * resource. That failing, this method will invoke {@link #getResource(String)} to find the resource. * * @param contextClass * The class whose class loader will first be searched * @param name * The resource name * @return A URL object for reading the resource, or null if the resource could not be found or the * invoker doesn't have adequate privileges to get the resource. */ public URL getResource(final Class contextClass, final String name) { if (contextClass != null) { URL url = contextClass.getResource(name); if (url != null) { return url; } } return getResource(name); } /** * Returns an input stream for reading the specified resource. * *

    * The search order is described in the documentation for {@link #getResource(String)}. *

    * * @param name * The resource name * * @return An input stream for reading the resource, or null if the resource could not be found */ public InputStream getResourceAsStream(final String name) { URL url = getResource(name); try { return url != null ? url.openStream() : null; } catch (IOException e) { return null; } } /** * Returns an input stream for reading the specified resource. *

    * The search order is described in the documentation for {@link #getResource(Class, String)}. * * @param contextClass * The class whose class loader will first be searched * @param name * The resource name * @return An input stream for reading the resource, or null if the resource could not be found */ public InputStream getResourceAsStream(final Class contextClass, final String name) { if (contextClass != null) { InputStream is = contextClass.getResourceAsStream(name); if (is != null) { return is; } } return getResourceAsStream(name); } /** * Finds all the resources with the given name. This method will first search * the class loader of the context class for the resource. That failing, this * method will invoke {@link #getResources(String)} to find the resource. * * @param contextClass * The class whose class loader will first be searched * * @param name * The resource name * * @return An enumeration of {@link java.net.URL URL} objects for * the resource. If no resources could be found, the enumeration * will be empty. Resources that the class loader doesn't have * access to will not be in the enumeration. * * @throws IOException * If I/O errors occur */ public Enumeration getResources(final Class contextClass, final String name) throws IOException { try { if (contextClass != null) { Enumeration urls = contextClass.getClassLoader().getResources(name); if (urls != null && urls.hasMoreElements()) { return urls; } } } catch (IOException ignore) { // fall through to invoke getResources(name) } return getResources(name); } /** * Interface to plugin a custom class loading mechanism (as opposed to a * custom ClassLoader). Used by GemFireXD for classes loaded into the instance * and persisted by sqlj.install_jar. * * @author swale * @since 7.0 */ public static interface ClassLoaderInterface { Class loadApplicationClass(String className) throws ClassNotFoundException; } /** * Finds all the resources with the given name. * * @param name * The resource name * * @return An enumeration of {@link java.net.URL URL} objects for * the resource. If no resources could be found, the enumeration * will be empty. Resources that the class loader doesn't have * access to will not be in the enumeration. * * @throws IOException * If I/O errors occur */ public Enumeration getResources(String name) throws IOException { if (ENABLE_TRACE) { trace(new StringBuilder("getResources(").append(name).append(")")); } Enumeration urls = null; ClassLoader tccl = null; if (!excludeTCCL) { tccl = Thread.currentThread().getContextClassLoader(); } IOException ioException = null; for (ClassLoader classLoader : this.classLoaders) { ioException = null; // reset to null for next ClassLoader if (classLoader == TCCL_PLACEHOLDER) { try { if (tccl != null) { if (ENABLE_TRACE) { trace(new StringBuilder("getResources trying TCCL: ").append(tccl)); } urls = tccl.getResources(name); if (urls != null && urls.hasMoreElements()) { if (ENABLE_TRACE) { trace("getResources found by TCCL"); } return urls; } } else { if (ENABLE_TRACE) { trace(new StringBuilder("getResources skipping TCCL because it's null")); } } } catch (SecurityException ignore) { // Continue to next ClassLoader } catch (IOException ignore) { ioException = ignore; // Continue to next ClassLoader } } else if (excludeTCCL || !classLoader.equals(tccl)) { try { if (ENABLE_TRACE) { trace(new StringBuilder("getResources trying classLoader: ").append(classLoader)); } urls = classLoader.getResources(name); if (urls != null && urls.hasMoreElements()) { if (ENABLE_TRACE) { trace(new StringBuilder("getResources found by classLoader: ").append(classLoader)); } return urls; } } catch (IOException ignore) { ioException = ignore; // Continue to next ClassLoader } } } if (ioException != null) { if (ENABLE_TRACE) { trace(new StringBuilder("getResources throwing IOException")); } throw ioException; } if (ENABLE_TRACE) { trace(new StringBuilder("getResources returning empty enumeration")); } return urls; } private void trace(final StringBuilder message) { trace(message.toString()); } private void trace(final String message) { String msg = new StringBuilder(toString()).append(" ").append(message).toString(); GemFireCacheImpl gfc = GemFireCacheImpl.getInstance(); if (gfc != null) { gfc.getLogger().fine(msg); } else { System.out.println(msg); } } private static void warning(final String message) { GemFireCacheImpl gfc = GemFireCacheImpl.getInstance(); if (gfc != null) { gfc.getLogger().warning(message); } else { System.err.println(message); } } public static ClassPathLoader getLatest() { return latest.get(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy