/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.hotswap.agent.plugin.resteasy; import org.hotswap.agent.annotation.FileEvent; import org.hotswap.agent.annotation.Init; import org.hotswap.agent.annotation.LoadEvent; import org.hotswap.agent.annotation.OnClassFileEvent; import org.hotswap.agent.annotation.OnClassLoadEvent; import org.hotswap.agent.annotation.Plugin; import org.hotswap.agent.command.Command; import org.hotswap.agent.command.Scheduler; import org.hotswap.agent.config.PluginConfiguration; import org.hotswap.agent.javassist.CannotCompileException; import org.hotswap.agent.javassist.ClassPool; import org.hotswap.agent.javassist.CtClass; import org.hotswap.agent.javassist.CtMethod; import org.hotswap.agent.javassist.NotFoundException; import org.hotswap.agent.javassist.bytecode.AccessFlag; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.logging.AgentLogger.Level; import org.hotswap.agent.util.AnnotationHelper; import org.hotswap.agent.util.PluginManagerInvoker; import org.hotswap.agent.util.ReflectionHelper; /** * RESTeasy plugin which cleanups and registers class redefinitions in the RESTeasy ResourceMethodRegistry * * @author alpapad@gmail.com * */ @Plugin(name = "ResteasyRegistry", // description = "Jboss RESTeasy Reload ResourceMethodRegistry if @Path annotated class is changed.", // testedVersions = { "3.0.14.Final" }, // expectedVersions = { "3.0.14" }) public class ResteasyRegistryPlugin { private static AgentLogger LOGGER = AgentLogger.getLogger(ResteasyRegistryPlugin.class); private static final String PATH_ANNOTATION = "javax.ws.rs.Path"; @Init ClassLoader appClassLoader; @Init Scheduler scheduler; Object servletContext; Object servletContainerDispatcher; /** * Patch ResourceMethodRegistry, make rootNode && root fields public * * @param ctClass * @param classPool */ @OnClassLoadEvent(classNameRegexp = "org.jboss.resteasy.core.ResourceMethodRegistry") public static void patchResourceMethodRegistry(CtClass ctClass, ClassPool classPool) { try { // Make ResourceMethodRegistry root nodes readable ctClass.getField("rootNode").setModifiers(AccessFlag.PUBLIC); ctClass.getField("root").setModifiers(AccessFlag.PUBLIC); } catch (NotFoundException e) { LOGGER.error("Error patching FilterDispatcher", e); } } /** * * @param ctClass * @param classPool */ @OnClassLoadEvent(classNameRegexp = "org.jboss.resteasy.plugins.server.servlet.FilterDispatcher") public static void patchFilterDispatcher(CtClass ctClass, ClassPool classPool) { try{ CtMethod init = ctClass.getDeclaredMethod("init"); init.insertAfter(""// +"java.lang.ClassLoader $$cl = Thread.currentThread().getContextClassLoader();" // +"java.lang.Object $$servletContext = servletConfig.getServletContext();"// + PluginManagerInvoker.buildInitializePlugin(ResteasyRegistryPlugin.class, "$$cl")// + PluginManagerInvoker.buildCallPluginMethod("$$cl", ResteasyRegistryPlugin.class, "registerContext", "$$servletContext", "java.lang.Object")// + PluginManagerInvoker.buildCallPluginMethod("$$cl", ResteasyRegistryPlugin.class, "registerServletContainerDispatcher", "servletContainerDispatcher", "java.lang.Object")// ); } catch(NotFoundException | CannotCompileException e){ LOGGER.error("Error patching FilterDispatcher", e); } } @OnClassLoadEvent(classNameRegexp = "org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher") public static void patchServletDispatcher(CtClass ctClass, ClassPool classPool){ try{ CtMethod init = ctClass.getDeclaredMethod("init"); init.insertAfter(""// + "java.lang.Object $$servletContext = servletConfig.getServletContext();" // + "java.lang.ClassLoader $$cl = Thread.currentThread().getContextClassLoader();"// + PluginManagerInvoker.buildInitializePlugin(ResteasyRegistryPlugin.class, "$$cl") // + PluginManagerInvoker.buildCallPluginMethod("$$cl", ResteasyRegistryPlugin.class, "registerContext", "$$servletContext", "java.lang.Object") // + PluginManagerInvoker.buildCallPluginMethod("$$cl", ResteasyRegistryPlugin.class, "registerServletContainerDispatcher", "servletContainerDispatcher", "java.lang.Object")// ); } catch(NotFoundException | CannotCompileException e){ LOGGER.error("Error patching HttpServletDispatcher", e); } } public void registerContext(Object servletContext) { this.servletContext = servletContext; LOGGER.info("Registered ServletContext {} ", servletContext); } public void registerServletContainerDispatcher(Object servletContainerDispatcher) { this.servletContainerDispatcher = servletContainerDispatcher; LOGGER.info("Registered ServletContainerDispatcher {} ", servletContainerDispatcher); } @OnClassLoadEvent(classNameRegexp = ".*", events = LoadEvent.REDEFINE) public void entityReload(ClassLoader classLoader, CtClass clazz, Class<?> original) { if (AnnotationHelper.hasAnnotation(original, PATH_ANNOTATION) || AnnotationHelper.hasAnnotation(clazz, PATH_ANNOTATION)) { if(LOGGER.isLevelEnabled(Level.TRACE)) { LOGGER.trace("Reload @Path annotated class {}", clazz.getName()); } refreshClass(classLoader, clazz.getName(), original, 250); } } @OnClassFileEvent(classNameRegexp = ".*", events = { FileEvent.CREATE }) public void newEntity(ClassLoader classLoader, CtClass clazz) throws Exception { if (AnnotationHelper.hasAnnotation(clazz, PATH_ANNOTATION)) { if(LOGGER.isLevelEnabled(Level.TRACE)) { LOGGER.trace("Load @Path annotated class {}", clazz.getName()); } refreshClass(classLoader, clazz.getName(), null, 500); } } private void refreshClass(ClassLoader classLoader, String name, Class<?> original, int timeout) { try { Class<?> cmdClass = Class.forName(RefreshRegistryCommand.class.getName(), true, appClassLoader); Command cmd = (Command) cmdClass.newInstance(); ReflectionHelper.invoke(cmd, cmdClass, "setupCmd", new Class[] { ClassLoader.class, Object.class, Object.class, String.class, java.lang.Class.class }, classLoader, servletContext, servletContainerDispatcher, name, original); scheduler.scheduleCommand(cmd, timeout); } catch (Exception e) { LOGGER.error("refreshClass() exception {}.", e.getMessage()); } } @Init public void initializeInstance(PluginConfiguration pluginConfiguration) { LOGGER.info("ResteasyRegistryPlugin Initializing"); } }