package org.hotswap.agent.plugin.jersey2;
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;
@Plugin(name = "Jersey2",
description = "Jersey2 framework plugin - this does not handle HK2 changes",
testedVersions = {"2.10.1"},
expectedVersions = {"2.10.1"})
public class Jersey2Plugin {
private static AgentLogger LOGGER = AgentLogger.getLogger(Jersey2Plugin.class);
@Init
Scheduler scheduler;
@Init
ClassLoader appClassLoader;
Set<Object> registeredJerseyContainers = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
// Set<Object> registeredServiceLocators = 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 = "org.glassfish.jersey.servlet.ServletContainer")
public static void jerseyServletCallInitialized(CtClass ctClass, ClassPool classPool) throws NotFoundException, CannotCompileException {
CtMethod init = ctClass.getDeclaredMethod("init", new CtClass[] { classPool.get("org.glassfish.jersey.servlet.WebConfig") });
init.insertBefore(PluginManagerInvoker.buildInitializePlugin(Jersey2Plugin.class));
LOGGER.info("org.glassfish.jersey.servlet.ServletContainer enhanced with plugin initialization.");
String registerThis = PluginManagerInvoker.buildCallPluginMethod(Jersey2Plugin.class, "registerJerseyContainer", "this",
"java.lang.Object", "getConfiguration()", "java.lang.Object"/*, "getApplicationHandler().getServiceLocator()", "java.lang.Object"*/);
init.insertAfter(registerThis);
// Workaround a Jersey issue where ServletContainer cannot be reloaded since it is in an immutable state
CtMethod reload = ctClass.getDeclaredMethod("reload", new CtClass[] { classPool.get("org.glassfish.jersey.server.ResourceConfig") });
reload.insertBefore("$1 = new org.glassfish.jersey.server.ResourceConfig($1);");
}
/**
* Fix a scanning issue with jersey pre-2.4 versions. https://java.net/jira/browse/JERSEY-1936
*/
@OnClassLoadEvent(classNameRegexp = "org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener")
public static void fixAnnoationAcceptingListener(CtClass ctClass) throws NotFoundException, CannotCompileException {
CtMethod process = ctClass.getDeclaredMethod("process");
process.insertAfter("try { $2.close(); } catch (Exception e) {}");
}
/**
* Fix CDI CDI_MULTIPLE_LOCATORS_INTO_SIMPLE_APP exception on class redefinition
*/
@OnClassLoadEvent(classNameRegexp = "org.glassfish.jersey.ext.cdi1x.internal.SingleHk2LocatorManager")
public static void fixSingleHk2LocatorManager(CtClass ctClass) throws NotFoundException, CannotCompileException {
CtMethod process = ctClass.getDeclaredMethod("registerLocator");
process.insertBefore("if (this.locator != null) return;");
LOGGER.debug("SingleHk2LocatorManager : patched()");
}
/**
* Register the jersey container and the classes involved in configuring the Jersey Application
*/
public void registerJerseyContainer(Object jerseyContainer, Object resourceConfig/*, Object serviceLocator*/) {
try {
Class<?> resourceConfigClass = resolveClass("org.glassfish.jersey.server.ResourceConfig");
LOGGER.info("registerJerseyContainer : " + jerseyContainer.getClass().getName());
Set<Class<?>> containerClasses = getContainerClasses(resourceConfigClass, resourceConfig);
registeredJerseyContainers.add(jerseyContainer);
allRegisteredClasses.addAll(containerClasses);
/*
if (serviceLocator != null) {
registeredServiceLocators.add(serviceLocator);
}
*/
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 {
Method scanClassesMethod = resourceConfigClass.getDeclaredMethod("scanClasses");
scanClassesMethod.setAccessible(true);
@SuppressWarnings("unchecked")
Set<Class<?>> scannedClasses = (Set<Class<?>>) scanClassesMethod.invoke(resourceConfig);
Method getRegisteredClassesMethod = resourceConfigClass.getDeclaredMethod("getRegisteredClasses");
getRegisteredClassesMethod.setAccessible(true);
@SuppressWarnings("unchecked")
Set<Class<?>> registeredClasses = (Set<Class<?>>)getRegisteredClassesMethod.invoke(resourceConfig);
Set<Class<?>> containerClasses = Collections.newSetFromMap(new WeakHashMap<Class<?>, Boolean>());
containerClasses.addAll(scannedClasses);
containerClasses.addAll(registeredClasses);
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 {
boolean reloaded = false;
if (allRegisteredClasses.contains(original)) {
scheduler.scheduleCommand(reloadJerseyContainers);
reloaded = true;
} 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
if (AnnotationHelper.hasAnnotation(original, "javax.ws.rs.Path")
|| AnnotationHelper.hasAnnotation(ctClass, "javax.ws.rs.Path")) {
allRegisteredClasses.add(original);
scheduler.scheduleCommand(reloadJerseyContainers);
reloaded = true;
}
}
if (!reloaded) {
// reload if HK2 Service class is changed
if (AnnotationHelper.hasAnnotation(original, "org.jvnet.hk2.annotations.Service")
|| AnnotationHelper.hasAnnotation(ctClass, "org.jvnet.hk2.annotations.Service")) {
scheduler.scheduleCommand(reloadJerseyContainers);
// TODO : reload SystemDescriptor in case of Service change?
// scheduler.scheduleCommand(disposeReflectionCaches);
}
}
}
/**
* Call reload on the Jersey Application
*/
private Command reloadJerseyContainers = new Command() {
public void executeCommand() {
try {
Class<?> containerClass = resolveClass("org.glassfish.jersey.server.spi.Container");
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);
}
}
};
/**
* Dispose service locators reflection caches
*/
/*
private Command disposeReflectionCaches = new Command() {
public void executeCommand() {
if (!registeredServiceLocators.isEmpty()) {
try {
LOGGER.debug("Disposing reflection caches");
for (Object serviceLocator : registeredServiceLocators) {
ReflectionHelper.invoke(serviceLocator, serviceLocator.getClass(), "clearReflectionCache", new Class[]{});
}
LOGGER.info("Reflection caches disposed.");
} catch (Exception e) {
LOGGER.error("executeCommand() exception {}.", e.getMessage());
}
}
}
};
*/
private Class<?> resolveClass(String name) throws ClassNotFoundException {
return Class.forName(name, true, appClassLoader);
}
}