package org.hotswap.agent.plugin.jersey1; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.Set; import java.util.WeakHashMap; import org.hotswap.agent.annotation.Init; import org.hotswap.agent.annotation.LoadEvent; 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.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.logging.AgentLogger; import org.hotswap.agent.util.AnnotationHelper; import org.hotswap.agent.util.PluginManagerInvoker; import org.hotswap.agent.util.ReflectionHelper; @Plugin(name = "Jersey1", description = "Jersey1 framework plugin - this does not handle HK2 changes", testedVersions = {"1.18.3"}, expectedVersions = {"1.x"}) public class Jersey1Plugin { private static AgentLogger LOGGER = AgentLogger.getLogger(Jersey1Plugin.class); @Init Scheduler scheduler; @Init ClassLoader appClassLoader; Set<Object> registeredJerseyContainers = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>()); Set<Class<?>> allRegisteredClasses = Collections.newSetFromMap(new WeakHashMap<Class<?>, Boolean>()); /** * Initialize the plugin when Jersey's ServletContainer.init(WebConfig config) is called. This is called from both init() for a servlet * and init(Config) for a filter. * * Also, add the ServletContainer to a list of registeredJerseyContainers so that we can call reload on it later when classes change */ @OnClassLoadEvent(classNameRegexp = "com.sun.jersey.spi.container.servlet.ServletContainer") public static void jerseyServletCallInitialized(CtClass ctClass, ClassPool classPool) throws NotFoundException, CannotCompileException { CtMethod init = ctClass.getDeclaredMethod("init", new CtClass[] { classPool.get("com.sun.jersey.spi.container.servlet.WebConfig") }); init.insertBefore(PluginManagerInvoker.buildInitializePlugin(Jersey1Plugin.class)); LOGGER.info("com.sun.jersey.spi.container.servlet.WebConfig enhanced with plugin initialization."); String registerThis = PluginManagerInvoker.buildCallPluginMethod(Jersey1Plugin.class, "registerJerseyContainer", "this", "java.lang.Object", "this.webComponent.getResourceConfig()", "java.lang.Object"); init.insertAfter(registerThis); } /** * Register the jersey container and the classes involved in configuring the Jersey Application */ public void registerJerseyContainer(Object jerseyContainer, Object resourceConfig) { try { Class<?> resourceConfigClass = resolveClass("com.sun.jersey.api.core.ResourceConfig"); LOGGER.info("registerJerseyContainer : " + jerseyContainer.getClass().getName()); Set<Class<?>> containerClasses = getContainerClasses(resourceConfigClass, resourceConfig); registeredJerseyContainers.add(jerseyContainer); allRegisteredClasses.addAll(containerClasses); LOGGER.debug("registerJerseyContainer : finished"); } catch (Exception e) { LOGGER.error("Error registering Jersey Container.", e); } } /** * Gets a list of classes used in configure the Jersey Application */ private Set<Class<?>> getContainerClasses(Class<?> resourceConfigClass, Object resourceConfig) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { Set<Class<?>> containerClasses = Collections.newSetFromMap(new WeakHashMap<Class<?>, Boolean>()); Set<Class<?>> providerClasses = (Set<Class<?>>) ReflectionHelper.invoke(resourceConfig, resourceConfigClass, "getProviderClasses", new Class[]{}); if (providerClasses != null) { containerClasses.addAll(providerClasses); } Set<Class<?>> rootResourceClasses = (Set<Class<?>>) ReflectionHelper.invoke(resourceConfig, resourceConfigClass, "getRootResourceClasses", new Class[]{}); if (rootResourceClasses != null) { containerClasses.addAll(rootResourceClasses); } return containerClasses; } /** * Call reload on the jersey Application when any class changes that is either involved in configuring * the Jersey Application, or if was newly annotated and will be involved in configuring the application. */ @OnClassLoadEvent(classNameRegexp = ".*", events = LoadEvent.REDEFINE) public void invalidate(CtClass ctClass, Class original) throws Exception { if (allRegisteredClasses.contains(original)) { scheduler.scheduleCommand(reloadJerseyContainers); } else { // TODO: When a class is not annotated at startup, and is annotated during debug, it never gets found // here. Is this a DCEVM issue? Also, the Jersey Container does not find the newly annotated class // during a reload called from reloadJerseyContainers, so this seems like the annotation is not being // added // vd: it is wrong here, since original class is scanned for Path ! if (AnnotationHelper.hasAnnotation(original, "javax.ws.rs.Path") || AnnotationHelper.hasAnnotation(ctClass, "javax.ws.rs.Path")) { allRegisteredClasses.add(original); scheduler.scheduleCommand(reloadJerseyContainers); } } } /** * Call reload on the Jersey Application */ private Command reloadJerseyContainers = new Command() { public void executeCommand() { try { Class<?> containerClass = resolveClass("com.sun.jersey.spi.container.servlet.ServletContainer"); Method reloadMethod = containerClass.getDeclaredMethod("reload"); for (Object jerseyContainer : registeredJerseyContainers) { reloadMethod.invoke(jerseyContainer); } LOGGER.info("Reloaded Jersey Containers"); } catch (Exception e) { LOGGER.error("Error reloading Jersey Container.", e); } } }; private Class<?> resolveClass(String name) throws ClassNotFoundException { return Class.forName(name, true, appClassLoader); } }