/*
* 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 java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
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.javassist.CannotCompileException;
import org.hotswap.agent.javassist.ClassPool;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.CtField;
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;
/**
* Resteasy
*
* @author Vladimir Dvorak - HotswapAgent implementation,
* @author Stuart Douglas - original concept in FakeReplace
*/
@Plugin(name = "Resteasy",
description = "Jboss RESTeasy framework (http://resteasy.jboss.org/). Reload FilterDispatcher / HttpServletDispatcher configurations "
+ "if @Path annotated class is changed.",
testedVersions = {"3.0.14.Final"},
expectedVersions = {"All between 2.x - 3.x"}
)
public class ResteasyPlugin {
private static AgentLogger LOGGER = AgentLogger.getLogger(ResteasyPlugin.class);
private static final String PATH_ANNOTATION = "javax.ws.rs.Path";
public static final String FIELD_NAME = "__config";
public static final String PARAMETER_FIELD_NAME = "__params";
@Init
ClassLoader appClassLoader;
@Init
Scheduler scheduler;
Set<Object> registeredDispatchers = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
@OnClassLoadEvent(classNameRegexp = "org.jboss.resteasy.plugins.server.servlet.FilterDispatcher")
public static void patchFilterDispatcher(CtClass ctClass, ClassPool classPool) throws NotFoundException, CannotCompileException {
CtClass fltCfgClass = classPool.get("javax.servlet.FilterConfig");
CtField configField = new CtField(fltCfgClass, FIELD_NAME, ctClass);
ctClass.addField(configField);
CtClass setClass = classPool.get(java.util.Set.class.getName());
CtField paramsField = new CtField(setClass, PARAMETER_FIELD_NAME, ctClass);
ctClass.addField(paramsField);
CtMethod methInit = ctClass.getDeclaredMethod("init");
methInit.insertBefore(
"{" +
" if(this." + PARAMETER_FIELD_NAME + " == null) {" +
PluginManagerInvoker.buildInitializePlugin(ResteasyPlugin.class) +
PluginManagerInvoker.buildCallPluginMethod(ResteasyPlugin.class, "registerDispatcher", "this", "java.lang.Object") +
" }" +
" this." + FIELD_NAME + " = $1;" +
" this." + PARAMETER_FIELD_NAME + " = " +
ResteasyContextParams.class.getName() + ".init($1.getServletContext(), this." + PARAMETER_FIELD_NAME +"); " +
"}"
);
}
@OnClassLoadEvent(classNameRegexp = "org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher")
public static void patchServletDispatcher(CtClass ctClass, ClassPool classPool) throws NotFoundException, CannotCompileException {
CtClass fltCfgClass = classPool.get("javax.servlet.ServletConfig");
CtField configField = new CtField(fltCfgClass, FIELD_NAME, ctClass);
ctClass.addField(configField);
CtClass setClass = classPool.get(java.util.Set.class.getName());
CtField paramsField = new CtField(setClass, PARAMETER_FIELD_NAME, ctClass);
ctClass.addField(paramsField);
CtMethod methInit = ctClass.getDeclaredMethod("init");
methInit.insertBefore(
"{" +
" if(this." + PARAMETER_FIELD_NAME + " == null) {" +
PluginManagerInvoker.buildInitializePlugin(ResteasyPlugin.class) +
PluginManagerInvoker.buildCallPluginMethod(ResteasyPlugin.class, "registerDispatcher", "this", "java.lang.Object") +
" }" +
" this." + FIELD_NAME + " = $1;" +
" this." + PARAMETER_FIELD_NAME + " = " +
ResteasyContextParams.class.getName() + ".init($1.getServletContext(), this." + PARAMETER_FIELD_NAME +"); " +
"}"
);
}
public void registerDispatcher(Object filterDispatcher) {
registeredDispatchers.add(filterDispatcher);
LOGGER.debug("RestEasyPlugin - dispatcher registered : " + filterDispatcher.getClass().getName());
}
@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)
) {
LOGGER.debug("Reload @Path annotated class {}, original classloader {}", clazz.getName(), original.getClassLoader());
refresh(classLoader, 100);
}
}
@OnClassFileEvent(classNameRegexp = ".*", events = {FileEvent.CREATE})
public void newEntity(ClassLoader classLoader, CtClass clazz) throws Exception {
if (AnnotationHelper.hasAnnotation(clazz, PATH_ANNOTATION)) {
refresh(classLoader, 500);
}
}
private void refresh(ClassLoader classLoader, int timeout) {
if (!registeredDispatchers.isEmpty()) {
try {
Class<?> cmdClass = Class.forName(RefreshDispatchersCommand.class.getName(), true, appClassLoader);
Command cmd = (Command) cmdClass.newInstance();
ReflectionHelper.invoke(cmd, cmdClass, "setupCmd",
new Class[] {java.lang.ClassLoader.class, java.util.Set.class}, classLoader, registeredDispatchers);
scheduler.scheduleCommand(cmd, timeout);
} catch (Exception e) {
LOGGER.error("refresh() exception {}.", e.getMessage());
}
}
}
}