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

org.apache.openejb.ClassLoaderUtil Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * 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.openejb;

import org.apache.openejb.classloader.ClassLoaderConfigurer;
import org.apache.openejb.classloader.CompositeClassLoaderConfigurer;
import org.apache.openejb.config.QuickJarsTxtParser;
import org.apache.openejb.core.TempClassLoader;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.UrlCache;
import org.apache.openejb.util.classloader.URLClassLoaderFirst;
import org.apache.xbean.recipe.ObjectRecipe;

import java.beans.Introspector;
import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.zip.ZipFile;

/**
 * @version $Revision: 1457261 $ $Date: 2013-03-16 15:13:22 +0000 (Sat, 16 Mar 2013) $
 */
public class ClassLoaderUtil {

    private static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, ClassLoaderUtil.class);
    private static final Map> classLoadersByApp = new HashMap>();
    private static final Map> appsByClassLoader = new HashMap>();
    private static final UrlCache localUrlCache = new UrlCache();

    public static void destroyClassLoader(final String appId, final String appPath) {
        destroyClassLoader(appId);
        destroyClassLoader(appPath);
    }

    public static ClassLoader getContextClassLoader() {
        return AccessController.doPrivileged(new PrivilegedAction() {

            @Override
            public ClassLoader run() {
                return Thread.currentThread().getContextClassLoader();
            }
        });
    }

    public static File getUrlCachedName(final String appId, final URL url) {
        return localUrlCache.getUrlCachedName(appId, url);
    }

    public static boolean isUrlCached(final String appId, final URL url) {
        return localUrlCache.isUrlCached(appId, url);
    }

    public static URL getUrlKeyCached(final String appId, final File file) {
        return localUrlCache.getUrlKeyCached(appId, file);
    }

    public static URLClassLoader createClassLoaderFirst(final String appId, final URL[] urls, final ClassLoader parent) {
        return cacheClassLoader(appId, new URLClassLoaderFirst(localUrlCache.cacheUrls(appId, urls), parent));
    }

    public static URLClassLoader createClassLoader(final String appId, final URL[] urls, final ClassLoader parent) {
        return cacheClassLoader(appId, new URLClassLoader(localUrlCache.cacheUrls(appId, urls), parent));
    }

    private static URLClassLoader cacheClassLoader(final String appId, final URLClassLoader classLoader) {
        List classLoaders = classLoadersByApp.get(appId);
        if (classLoaders == null) {
            classLoaders = new ArrayList(2);
            classLoadersByApp.put(appId, classLoaders);
        }
        classLoaders.add(classLoader);

        Set apps = appsByClassLoader.get(classLoader);
        if (apps == null) {
            apps = new LinkedHashSet(1);
            appsByClassLoader.put(classLoader, apps);
        }
        apps.add(appId);

        return classLoader;
    }

    /**
     * Destroy a classloader as forcefully as possible.
     *
     * @param classLoader ClassLoader to destroy.
     */
    public static void destroyClassLoader(final ClassLoader classLoader) {

        // remove from the indexes
        final Set apps = appsByClassLoader.remove(classLoader);

        logger.debug("Destroying classLoader '" + toString(classLoader) + "' for apps: " + apps);

        if (apps != null) {

            List classLoaders;

            for (final String appId : apps) {

                classLoaders = classLoadersByApp.get(appId);

                if (classLoaders != null) {
                    classLoaders.remove(classLoader);
                }

                //If this is the last class loader in the app, clean up the app
                if (null == classLoaders || classLoaders.isEmpty()) {
                    destroyClassLoader(appId);
                }
            }
        }

        // Clear OpenJPA caches
        cleanOpenJPACache(classLoader);

        //Clear open jar files belonging to this ClassLoader
        for (final String jar : getClosedJarFiles(classLoader)) {
            clearSunJarFileFactoryCache(jar);
        }
    }

    /**
     * Dirty hack to force closure of file handles in the Oracle VM URLClassLoader
     * Any URLClassLoader passed into this method will be unusable after the method completes.
     *
     * @param cl ClassLoader of expected type URLClassLoader (Silent failure)
     */
    private static List getClosedJarFiles(final ClassLoader cl) {

        final List files = new ArrayList();

        if (null != cl && cl instanceof URLClassLoader) {

            final URLClassLoader ucl = (URLClassLoader) cl;
            final Class clazz = java.net.URLClassLoader.class;

            try {

                final java.lang.reflect.Field ucp = clazz.getDeclaredField("ucp");
                ucp.setAccessible(true);
                final Object cp = ucp.get(ucl);
                final java.lang.reflect.Field loaders = cp.getClass().getDeclaredField("loaders");
                loaders.setAccessible(true);
                final java.util.Collection c = (java.util.Collection) loaders.get(cp);
                java.lang.reflect.Field loader;
                java.util.jar.JarFile jf;

                for (final Object jl : c.toArray()) {
                    try {
                        loader = jl.getClass().getDeclaredField("jar");
                        loader.setAccessible(true);
                        jf = (java.util.jar.JarFile) loader.get(jl);
                        files.add(jf.getName());
                        jf.close();
                    } catch (Throwable t) {
                        //If we got this far, this is probably not a JAR loader so skip it
                    }
                }
            } catch (Throwable t) {
                //Not an Oracle VM
            }
        }

        return files;
    }

    @SuppressWarnings("UseOfObsoleteCollectionType")
    public boolean finalizeNativeLibs(final ClassLoader cl) {

        boolean res = false;
        final Class classClassLoader = ClassLoader.class;
        java.lang.reflect.Field nativeLibraries = null;

        try {
            nativeLibraries = classClassLoader.getDeclaredField("nativeLibraries");
        } catch (NoSuchFieldException e1) {
            //Ignore
        }

        if (nativeLibraries == null) {
            return res;
        }

        nativeLibraries.setAccessible(true);
        Object obj = null;

        try {
            obj = nativeLibraries.get(cl);
        } catch (IllegalAccessException e1) {
            //Ignore
        }

        if (!(obj instanceof Vector)) {
            return res;
        }

        res = true;
        final Vector java_lang_ClassLoader_NativeLibrary = (Vector) obj;
        java.lang.reflect.Method finalize;

        for (final Object lib : java_lang_ClassLoader_NativeLibrary) {

            try {
                finalize = lib.getClass().getDeclaredMethod("finalize", new Class[0]);

                if (finalize != null) {

                    finalize.setAccessible(true);

                    try {
                        finalize.invoke(lib, new Object[0]);
                    } catch (Throwable e) {
                        //Ignore
                    }
                }
            } catch (Throwable e) {
                //Ignore
            }
        }
        return res;
    }

    public static void destroyClassLoader(final String appId) {

        logger.debug("Destroying classLoaders for application " + appId);
        final List classLoaders = classLoadersByApp.remove(appId);

        if (classLoaders != null) {

            final Iterator it = classLoaders.iterator();
            Set apps;
            ClassLoader cl;

            while (it.hasNext()) {

                cl = it.next();
                apps = appsByClassLoader.get(cl);

                if (null != apps) {
                    //This app is no longer using the class loader
                    apps.remove(appId);
                }

                //If no apps are using the class loader, destroy it
                if (null == apps || apps.isEmpty()) {
                    it.remove();
                    appsByClassLoader.remove(cl);
                    destroyClassLoader(cl);
                    System.gc();
                } else {
                    logger.debug("ClassLoader " + toString(cl) + " held open by the applications: " + apps);
                }
            }
        }

        localUrlCache.releaseUrls(appId);
        clearSunJarFileFactoryCache(appId);
    }

    public static URLClassLoader createTempClassLoader(final ClassLoader parent) {
        return new TempClassLoader(parent);
    }

    public static URLClassLoader createTempClassLoader(final String appId, final URL[] rawUrls, final ClassLoader parent) {
        String updatedAppId = appId;
        if (appId != null) { // here we often get the full path of the app as id where later it is simply the name of the file/dir
            final File file = new File(appId);
            if (file.exists()) {
                updatedAppId = file.getName();
                if (updatedAppId.endsWith(".war") || updatedAppId.endsWith(".ear")) {
                    updatedAppId = updatedAppId.substring(0, updatedAppId.length() - ".war".length());
                }
            }
        }

        // from the app
        final ClassLoaderConfigurer configurer1 = QuickJarsTxtParser.parse(new File(appId, "META-INF/" + QuickJarsTxtParser.FILE_NAME));
        final ClassLoaderConfigurer configurer2 = QuickJarsTxtParser.parse(new File(appId, "WEB-INF/" + QuickJarsTxtParser.FILE_NAME));

        // external config
        ClassLoaderConfigurer configurer3 = ClassLoaderUtil.configurer(updatedAppId);
        if (configurer3 == null) { // try the complete path
            configurer3 = ClassLoaderUtil.configurer(appId);
        }

        final URL[] urls;
        if (configurer1 == null && configurer2 == null && configurer3 == null) {
            urls = rawUrls;
        } else {
            final CompositeClassLoaderConfigurer configurer = new CompositeClassLoaderConfigurer(configurer1, configurer2, configurer3);
            final Collection list = new ArrayList();
            list.addAll(Arrays.asList(rawUrls));
            ClassLoaderConfigurer.Helper.configure(list, configurer);
            urls = list.toArray(new URL[list.size()]);
        }

        return new TempClassLoader(createClassLoader(appId, urls, parent));
    }

    /**
     * Cleans well known class loader leaks in VMs and libraries.  There is a lot of bad code out there and this method
     * will clear up the know problems.  This method should only be called when the class loader will no longer be used.
     * It this method is called two often it can have a serious impact on preformance.
     */
    public static void clearClassLoaderCaches() {
        clearSunSoftCache(ObjectInputStream.class, "subclassAudits");
        clearSunSoftCache(ObjectOutputStream.class, "subclassAudits");
        clearSunSoftCache(ObjectStreamClass.class, "localDescs");
        clearSunSoftCache(ObjectStreamClass.class, "reflectors");
        Introspector.flushCaches();
    }

    public static void clearSunJarFileFactoryCache(final String jarLocation) {
        clearSunJarFileFactoryCacheImpl(jarLocation, 5);
    }

    /**
     * Due to several different implementation changes in various JDK releases the code here is not as
     * straight forward as reflecting debug items in your current runtime. There have even been breaking changes
     * between 1.6 runtime builds, let alone 1.5.
     * 

* If you discover a new issue here please be careful to ensure the existing functionality is 'extended' and not * just replaced to match your runtime observations. *

* If you want to look at the mess that leads up to this then follow the source code changes made to * the class sun.net.www.protocol.jar.JarFileFactory over several years. * * @param jarLocation String * @param attempt int */ @SuppressWarnings({"unchecked"}) private static synchronized void clearSunJarFileFactoryCacheImpl(final String jarLocation, final int attempt) { logger.debug("Clearing Sun JarFileFactory cache for directory " + jarLocation); try { final Class jarFileFactory = Class.forName("sun.net.www.protocol.jar.JarFileFactory"); //Do not generify these maps as their contents are NOT stable across runtimes. final Field fileCacheField = jarFileFactory.getDeclaredField("fileCache"); fileCacheField.setAccessible(true); final Map fileCache = (Map) fileCacheField.get(null); final Map fileCacheCopy = new HashMap(fileCache); final Field urlCacheField = jarFileFactory.getDeclaredField("urlCache"); urlCacheField.setAccessible(true); final Map urlCache = (Map) urlCacheField.get(null); final Map urlCacheCopy = new HashMap(urlCache); //The only stable item we have here is the JarFile/ZipFile in this map Iterator iterator = urlCacheCopy.entrySet().iterator(); final List urlCacheRemoveKeys = new ArrayList(); while (iterator.hasNext()) { final Map.Entry entry = (Map.Entry) iterator.next(); final Object key = entry.getKey(); if (key instanceof ZipFile) { final ZipFile zf = (ZipFile) key; final File file = new File(zf.getName()); //getName returns File.getPath() if (isParent(jarLocation, file)) { //Flag for removal urlCacheRemoveKeys.add(key); } } else { logger.warning("Unexpected key type: " + key); } } iterator = fileCacheCopy.entrySet().iterator(); final List fileCacheRemoveKeys = new ArrayList(); while (iterator.hasNext()) { final Map.Entry entry = (Map.Entry) iterator.next(); final Object value = entry.getValue(); if (urlCacheRemoveKeys.contains(value)) { fileCacheRemoveKeys.add(entry.getKey()); } } //Use these unstable values as the keys for the fileCache values. iterator = fileCacheRemoveKeys.iterator(); while (iterator.hasNext()) { final Object next = iterator.next(); try { final Object remove = fileCache.remove(next); if (null != remove) { logger.debug("Removed item from fileCache: " + remove); } } catch (Throwable e) { logger.warning("Failed to remove item from fileCache: " + next); } } iterator = urlCacheRemoveKeys.iterator(); while (iterator.hasNext()) { final Object next = iterator.next(); try { final Object remove = urlCache.remove(next); try { ((ZipFile) next).close(); } catch (Throwable e) { //Ignore } if (null != remove) { logger.debug("Removed item from urlCache: " + remove); } } catch (Throwable e) { logger.warning("Failed to remove item from urlCache: " + next); } } } catch (ConcurrentModificationException e) { if (attempt > 0) { clearSunJarFileFactoryCacheImpl(jarLocation, (attempt - 1)); } else { logger.error("Unable to clear Sun JarFileFactory cache after 5 attempts", e); } } catch (ClassNotFoundException e) { // not a sun vm } catch (NoSuchFieldException e) { // different version of sun vm? } catch (Throwable e) { logger.error("Unable to clear Sun JarFileFactory cache", e); } } private static boolean isParent(final String jarLocation, File file) { final File dir = new File(jarLocation); while (file != null) { if (file.equals(dir)) { return true; } file = file.getParentFile(); } return false; } /** * Clears the caches maintained by the SunVM object stream implementation. * This method uses reflection and setAccessable to obtain access to the Sun cache. * The cache Class synchronizes upon itself for access to the cache Map. * This method completely clears the class loader cache which will impact preformance of object serialization. * * @param clazz the name of the class containing the cache field * @param fieldName the name of the cache field */ public static void clearSunSoftCache(final Class clazz, final String fieldName) { try { final Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); final Map cache = (Map) field.get(null); cache.clear(); } catch (Throwable ignored) { // there is nothing a user could do about this anyway } } public static void cleanOpenJPACache(final ClassLoader classLoader) { try { final Class pcRegistryClass = ClassLoaderUtil.class.getClassLoader().loadClass("org.apache.openjpa.enhance.PCRegistry"); final Method deRegisterMethod = pcRegistryClass.getMethod("deRegister", ClassLoader.class); deRegisterMethod.invoke(null, classLoader); } catch (Throwable ignored) { // there is nothing a user could do about this anyway } } private static String toString(final ClassLoader classLoader) { if (classLoader == null) { return "null"; } else { return classLoader.getClass().getSimpleName() + "@" + System.identityHashCode(classLoader); } } public static String resourceName(final String s) { return s.replace(".", "/") + ".class"; } public static ClassLoaderConfigurer configurer(final String rawId) { String id = rawId; if (id != null && (id.startsWith("/") || id.startsWith("\\")) && !new File(id).exists() && id.length() > 1) { id = id.substring(1); } if (id == null) { id = ""; } // TODO: see how to manage tomee/openejb prefix String key = "tomee.classloader.configurer." + id + ".clazz"; String impl = SystemInstance.get().getProperty(key); if (impl == null) { key = "tomee.classloader.configurer.clazz"; impl = SystemInstance.get().getProperty(key); if (impl == null) { key = "openejb.classloader.configurer." + id + ".clazz"; impl = SystemInstance.get().getProperty(key); if (impl == null) { key = "openejb.classloader.configurer.clazz"; impl = SystemInstance.get().getProperty(key); } } } if (impl != null) { key = key.substring(0, key.length() - "clazz".length()); boolean list = false; try { ClassLoaderUtil.class.getClassLoader().loadClass(impl); } catch (ClassNotFoundException e) { list = true; } if (!list) { return createConfigurer(key, impl); } else { final String[] names = impl.split(","); final ClassLoaderConfigurer[] configurers = new ClassLoaderConfigurer[names.length]; for (int i = 0; i < names.length; i++) { configurers[i] = createConfigurer(names[i], SystemInstance.get().getProperty(names[i] + ".clazz")); } return new CompositeClassLoaderConfigurer(configurers); } } return null; } private static ClassLoaderConfigurer createConfigurer(final String key, final String impl) { try { final ObjectRecipe recipe = new ObjectRecipe(impl); for (final Map.Entry entry : SystemInstance.get().getProperties().entrySet()) { final String entryKey = entry.getKey().toString(); if (entryKey.startsWith(key)) { final String newKey = entryKey.substring(key.length()); if (!"clazz".equals(newKey)) { recipe.setProperty(newKey, entry.getValue()); } } } final Object instance = recipe.create(); if (instance instanceof ClassLoaderConfigurer) { return (ClassLoaderConfigurer) instance; } else { logger.error(impl + " is not a classlaoder configurer, using default behavior"); } } catch (Exception e) { logger.error("Can't create classloader configurer " + impl + ", using default behavior"); } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy