package org.hotswap.agent.util.classloader; import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.Enumeration; import org.hotswap.agent.logging.AgentLogger; import sun.misc.URLClassPath; /** * Helper methods to enhance URL ClassLoader. */ public class URLClassLoaderHelper { private static AgentLogger LOGGER = AgentLogger.getLogger(URLClassLoaderHelper.class); /** * Insert classpath at the beginning of the classloader path. * This implementation will replace ucp field (URLClassPath) with new definition. Any existing Loader * is discarded and recreated. * * @param classLoader URL classloader * @param extraClassPath path to prepend */ public static void prependClassPath(URLClassLoader classLoader, URL[] extraClassPath) { synchronized (classLoader) { // set new URLClassPath to the classloader via reflection try { Field ucpField = URLClassLoader.class.getDeclaredField("ucp"); ucpField.setAccessible(true); URL[] origClassPath = getOrigClassPath(classLoader, ucpField); URL[] modifiedClassPath = new URL[origClassPath.length + extraClassPath.length]; System.arraycopy(extraClassPath, 0, modifiedClassPath, 0, extraClassPath.length); System.arraycopy(origClassPath, 0, modifiedClassPath, extraClassPath.length, origClassPath.length); ucpField.set(classLoader, new ExtraURLClassPath(modifiedClassPath)); LOGGER.debug("Added extraClassPath URLs {} to classLoader {}", Arrays.toString(extraClassPath), classLoader); } catch (Exception e) { LOGGER.error("Unable to add extraClassPath URLs {} to classLoader {}", e, Arrays.toString(extraClassPath), classLoader); } } } public static void setWatchResourceLoader(URLClassLoader classLoader, final ClassLoader watchResourceLoader) { synchronized (classLoader) { // set new URLClassPath to the classloader via reflection try { Field ucpField = URLClassLoader.class.getDeclaredField("ucp"); ucpField.setAccessible(true); URL[] origClassPath = getOrigClassPath(classLoader, ucpField); ucpField.set(classLoader, new ExtraURLClassPath(origClassPath, watchResourceLoader)); LOGGER.debug("WatchResourceLoader registered to classLoader {}", classLoader); } catch (Exception e) { LOGGER.debug("Unable to register WatchResourceLoader to classLoader {}", e, classLoader); } } } private static URL[] getOrigClassPath(URLClassLoader classLoader, Field ucpField) throws IllegalAccessException { URL[] origClassPath; URLClassPath urlClassPath = (URLClassPath) ucpField.get(classLoader); if (urlClassPath instanceof ExtraURLClassPath) { origClassPath = ((ExtraURLClassPath)urlClassPath).getOrigClassPath(); } else { origClassPath = classLoader.getURLs(); } return origClassPath; } private static class ExtraURLClassPath extends sun.misc.URLClassPath { private ClassLoader watchResourceLoader; URL[] origClassPath; public ExtraURLClassPath(URL[] origClassPath) { super(origClassPath); this.origClassPath = origClassPath; } public ExtraURLClassPath(URL[] origClassPath, ClassLoader watchResourceLoader) { super(origClassPath); this.origClassPath = origClassPath; this.watchResourceLoader = watchResourceLoader; } @Override public URL findResource(String name, boolean check) { if (watchResourceLoader != null) { URL resource = watchResourceLoader.getResource(name); if (resource != null) { return resource; } } return super.findResource(name, check); } public Enumeration<URL> findResources(final String name, boolean check) { if (watchResourceLoader != null) { try { Enumeration<URL> resources = watchResourceLoader.getResources(name); if (resources != null && resources.hasMoreElements()) { return resources; } } catch (IOException e) { LOGGER.debug("Unable to load resource {}", e, name); } } return super.findResources(name, check); } /** * Return orig classpath as was set by hotswap agent. * Note: cannot use classLoader.getURLs(), because Tomcat WebappClassLoader does not return modified classPath. */ public URL[] getOrigClassPath() { return origClassPath; } } }