package org.hotswap.agent.plugin.tomcat; import org.hotswap.agent.annotation.OnClassLoadEvent; import org.hotswap.agent.javassist.CannotCompileException; import org.hotswap.agent.javassist.ClassPool; import org.hotswap.agent.javassist.CtClass; import org.hotswap.agent.javassist.CtConstructor; import org.hotswap.agent.javassist.CtPrimitiveType; import org.hotswap.agent.javassist.NotFoundException; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.util.PluginManagerInvoker; /** * Created by bubnik on 9.6.2014. */ public class WebappLoaderTransformer { private static AgentLogger LOGGER = AgentLogger.getLogger(WebappLoaderTransformer.class); private static boolean webappClassLoaderPatched = false; /** * Init the plugin from start method. * * Hook into main init method of the loader. Init method name and resources type changes between * Tomcat versions. */ @OnClassLoadEvent(classNameRegexp = "org.apache.catalina.loader.WebappLoader") public static void patchWebappLoader(CtClass ctClass) throws NotFoundException, CannotCompileException, ClassNotFoundException { // handled by various Tomcat versions boolean startHandled = false; boolean stopHandled = false; // tomcat 8x if (!startHandled) { try { // fail for older tomcat version, which does not contain context ctClass.getDeclaredMethod("getContext"); ctClass.getDeclaredMethod("startInternal").insertAfter( TomcatPlugin.class.getName() + ".init(getClassLoader(), getContext().getResources());" ); startHandled = true; } catch (NotFoundException e) { LOGGER.trace("WebappLoader does not contain getContext() method, trying older Tomcat version."); } } // tomcat 7x if (!startHandled) { try { ctClass.getDeclaredMethod("startInternal").insertAfter( TomcatPlugin.class.getName() + ".init(getClassLoader(), container.getResources());" ); startHandled = true; } catch (NotFoundException e) { LOGGER.trace("WebappLoader does not contain startInternal() method, trying older Tomcat version."); } } // tomcat 6x if (!startHandled) { try { ctClass.getDeclaredMethod("start").insertAfter( TomcatPlugin.class.getName() + ".init(getClassLoader(), container.getResources());" ); startHandled = true; } catch (NotFoundException e) { } } if (!startHandled) { LOGGER.warning("org.apache.catalina.loader.WebappLoader does not contain neither start nor startInternal method. Tomcat plugin will be disabled.\n" + "*** Some properties (extraClasspath, watchResources) will NOT be supported on Tomcat level. They might be handled by another plugin though. ***"); } // tomcat 7x,8x if (!stopHandled) { try { ctClass.getDeclaredMethod("stopInternal").insertBefore( PluginManagerInvoker.buildCallCloseClassLoader("getClassLoader()") ); stopHandled = true; } catch (NotFoundException e) { LOGGER.debug("org.apache.catalina.core.StandardContext does not contain stopInternal() method, trying older stop() method."); } } // tomcat 6x if (!stopHandled) { try { ctClass.getDeclaredMethod("stop").insertBefore( PluginManagerInvoker.buildCallCloseClassLoader("getClassLoader()") ); stopHandled = true; } catch (NotFoundException e) { LOGGER.debug("org.apache.catalina.core.StandardContext does not contain neither stop() nor stopInternal() method. Hotswap agent will not be able to free Tomcat plugin resources."); } } } /** * Resource lookup for Tomcat 8x. * * Before the resource is handled by Tomcat, try to get extraResource handled by the plugin. */ @OnClassLoadEvent(classNameRegexp = "org.apache.catalina.webresources.StandardRoot") public static void patchStandardRoot(ClassPool classPool, CtClass ctClass) throws NotFoundException, CannotCompileException, ClassNotFoundException { CtClass ctFileResource = classPool.get("org.apache.catalina.webresources.FileResource"); CtConstructor ctFileResourceConstructor = ctFileResource.getConstructors()[0]; CtClass[] constructorTypes = ctFileResourceConstructor.getParameterTypes(); String constrParams; if (constructorTypes.length == 4) constrParams = "this, path, file, false"; else if (constructorTypes.length == 5) constrParams = "this, path, file, false, null"; else { LOGGER.warning("org.apache.catalina.webresources.FileResource unknown constructor. Tomcat plugin will not work properly."); return; } try { ctClass.getDeclaredMethod("getResourceInternal", new CtClass[]{classPool.get(String.class.getName()), CtPrimitiveType.booleanType}).insertBefore( "java.io.File file = " + TomcatPlugin.class.getName() + ".getExtraResourceFile(this, path);" + "if (file != null) return new org.apache.catalina.webresources.FileResource(" + constrParams + ");" ); } catch (NotFoundException e) { LOGGER.warning("org.apache.catalina.webresources.StandardRoot does not contain getResourceInternal method. Tomcat plugin will not work properly."); return; } // if getResources() should contain extraClasspath, expand the original returned array and prepend extraClasspath result try { ctClass.getDeclaredMethod("getResources", new CtClass[]{classPool.get(String.class.getName()), CtPrimitiveType.booleanType}).insertAfter( "java.io.File file = " + TomcatPlugin.class.getName() + ".getExtraResourceFile(this, path);" + "if (file != null) {" + "org.apache.catalina.WebResource[] ret = new org.apache.catalina.WebResource[$_.length + 1];" + "ret[0] = new org.apache.catalina.webresources.FileResource(" + constrParams + ");" + "java.lang.System.arraycopy($_, 0, ret, 1, $_.length);" + "return ret;" + "} else {return $_;}" ); } catch (NotFoundException e) { LOGGER.warning("org.apache.catalina.webresources.StandardRoot does not contain getResourceInternal method. Tomcat plugin will not work properly."); return; } } /** * Resource lookup for Tomcat 6x, 7x. * * Before the resource is handled by Tomcat, try to get extraResource handled by the plugin. */ @OnClassLoadEvent(classNameRegexp = "org.apache.naming.resources.ProxyDirContext") public static void patchProxyDirContext(ClassPool classPool, CtClass ctClass) throws NotFoundException, CannotCompileException, ClassNotFoundException { try { ctClass.getDeclaredMethod("lookup", new CtClass[] {classPool.get(String.class.getName())}).insertBefore( "java.io.InputStream is = " + TomcatPlugin.class.getName() + ".getExtraResource(this, name);" + "if (is != null) return new org.apache.naming.resources.Resource(is);" ); ctClass.getDeclaredMethod("getAttributes", new CtClass[] {classPool.get(String.class.getName())}).insertBefore( "long length = " + TomcatPlugin.class.getName() + ".getExtraResourceLength(this, name);" + "if (length > 0) {" + " org.apache.naming.resources.ResourceAttributes a = new org.apache.naming.resources.ResourceAttributes();" + " a.setContentLength(length); return a;" + "}" ); } catch (NotFoundException e) { LOGGER.warning("org.apache.naming.resources.ProxyDirContext does not contain lookup method. Tomcat plugin will not work properly."); return; } } /** * Disable loader caches - Tomcat 7x */ @OnClassLoadEvent(classNameRegexp = "org.apache.catalina.core.StandardContext") public static void patchStandardContext(ClassPool classPool, CtClass ctClass) throws NotFoundException, CannotCompileException, ClassNotFoundException { try { // force disable caching ctClass.getDeclaredMethod("isCachingAllowed").setBody("return false;"); } catch (NotFoundException e) { LOGGER.debug("org.apache.catalina.core.StandardContext does not contain isCachingAllowed() method (not 7x version)."); } // Tomcat version // this.getServletContext().getServerInfo() } /** * Brute force clear caches before any resource is loaded. * WebappClassLoader - Tomcat 7x * WebappClassLoaderBase - Tomcat 8x */ @OnClassLoadEvent(classNameRegexp = "(org.apache.catalina.loader.WebappClassLoader)|(org.apache.catalina.loader.WebappClassLoaderBase)") public static void patchWebappClassLoader(ClassPool classPool,CtClass ctClass) throws CannotCompileException, NotFoundException { if (!webappClassLoaderPatched) { try { // clear classloader cache ctClass.getDeclaredMethod("getResource", new CtClass[]{classPool.get("java.lang.String")}).insertBefore( "resourceEntries.clear();" ); ctClass.getDeclaredMethod("getResourceAsStream", new CtClass[]{classPool.get("java.lang.String")}).insertBefore( "resourceEntries.clear();" ); webappClassLoaderPatched = true; } catch (NotFoundException e) { LOGGER.trace("WebappClassLoader does not contain getResource(), getResourceAsStream method."); } } } }