package org.hotswap.agent.plugin.hibernate;
import java.lang.reflect.Method;
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.Manifest;
import org.hotswap.agent.annotation.OnClassFileEvent;
import org.hotswap.agent.annotation.OnClassLoadEvent;
import org.hotswap.agent.annotation.Plugin;
import org.hotswap.agent.annotation.Versions;
import org.hotswap.agent.annotation.Maven;
import org.hotswap.agent.annotation.Name;
import org.hotswap.agent.command.Command;
import org.hotswap.agent.command.ReflectionCommand;
import org.hotswap.agent.command.Scheduler;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.util.AnnotationHelper;
/**
* Reload Hibernate configuration after entity create/change.
*
* @author Jiri Bubnik
*/
@Plugin(name = "Hibernate",
group = "groupHibernate",
fallback = true,
description = "Reload Hibernate configuration after entity create/change.",
testedVersions = {"All between 4.0.1 - 4.2.13"},
expectedVersions = {"4.0.x", "4.1.x", "4.2.x", "5.0.[0-4,7-x]", "5.1.x", "5.2.x" },
supportClass = {HibernateTransformers.class})
@Versions(
maven = {
@Maven(value = "[4.0,6.0)", artifactId = "hibernate-core", groupId = "org.hibernate"),
@Maven(value = "[4.0,6.0)", artifactId = "hibernate-entitymanager", groupId = "org.hibernate"),
},
manifest= {
@Manifest(value="[4.0,6.0)", names= {
@Name(key=Name.BundleSymbolicName, value="org.hibernate.validator")
}),
@Manifest(value="[4.0,6.0)", names= {
@Name(key=Name.BundleSymbolicName, value="org.hibernate.entitymanager")
}),
@Manifest(value="[4.0,6.0)", names= {
@Name(key=Name.BundleSymbolicName, value="org.hibernate.core")
}),
@Manifest(value="[4.0,6.0)", names= {
@Name(key=Name.ImplementationUrl, value="http://hibernate.org"),
@Name(key=Name.ImplementationVendorId, value="org.hibernate")
}),
}
)
public class HibernatePlugin {
private static final String ENTITY_ANNOTATION = "javax.persistence.Entity";
private static AgentLogger LOGGER = AgentLogger.getLogger(HibernatePlugin.class);
@Init
Scheduler scheduler;
@Init
ClassLoader appClassLoader;
Set<Object> regAnnotatedMetaDataProviders = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
Set<Object> regBeanMetaDataManagers = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
// refresh commands
Command reloadEntityManagerFactoryCommand =
new ReflectionCommand(this, HibernateRefreshCommands.class.getName(), "reloadEntityManagerFactory");
Command reloadSessionFactoryCommand =
new ReflectionCommand(this, HibernateRefreshCommands.class.getName(), "reloadSessionFactory");
private Command invalidateHibernateValidatorCaches = new Command() {
@Override
public void executeCommand() {
LOGGER.debug("Refreshing BeanMetaDataManagerCache/AnnotatedMetaDataProvider cache.");
try {
Method resetCacheMethod1 = resolveClass("org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider").getDeclaredMethod("__resetCache");
for (Object regAnnotatedDataManager : regAnnotatedMetaDataProviders) {
LOGGER.debug("Invoking org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.__resetCache on {}", regAnnotatedDataManager);
resetCacheMethod1.invoke(regAnnotatedDataManager);
}
Method resetCacheMethod2 = resolveClass("org.hibernate.validator.internal.metadata.BeanMetaDataManager").getDeclaredMethod("__resetCache");
for (Object regBeanMetaDataManager : regBeanMetaDataManagers) {
LOGGER.debug("Invoking org.hibernate.validator.internal.metadata.BeanMetaDataManager.__resetCache on {}", regBeanMetaDataManager);
resetCacheMethod2.invoke(regBeanMetaDataManager);
}
} catch (Exception e) {
LOGGER.error("Error refreshing BeanMetaDataManagerCache/AnnotatedMetaDataProvider cache.", e);
}
}
};
// is EJB3 or plain hibernate
boolean hibernateEjb;
/**
* Plugin initialization properties (from HibernatePersistenceHelper or SessionFactoryProxy)
*/
public void init(String version, Boolean hibernateEjb) {
LOGGER.info("Hibernate plugin initialized - Hibernate Core version '{}'", version);
this.hibernateEjb = hibernateEjb;
}
/**
* Reload after entity class change. It covers also @Entity annotation removal.
*/
@OnClassLoadEvent(classNameRegexp = ".*", events = LoadEvent.REDEFINE)
public void entityReload(CtClass clazz, Class original) {
// TODO list of entity/resource files is known to hibernate, better to check this list
if (AnnotationHelper.hasAnnotation(original, ENTITY_ANNOTATION)
|| AnnotationHelper.hasAnnotation(clazz, ENTITY_ANNOTATION)
) {
LOGGER.debug("Entity reload class {}, original classloader {}", clazz.getName(), original.getClassLoader());
refresh(100);
}
}
/**
* New entity class - not covered by reloading mechanism.
* <p/>
* Increase the reload timeout to avoid duplicate reloading in case of recompile with IDE
* and delete/create event sequence - than create is cached by this event and hotswap for
* the same class by entityReload.
*/
@OnClassFileEvent(classNameRegexp = ".*", events = {FileEvent.CREATE})
public void newEntity(CtClass clazz) throws Exception {
if (AnnotationHelper.hasAnnotation(clazz, ENTITY_ANNOTATION)) {
refresh(500);
}
}
@OnClassLoadEvent(classNameRegexp = ".*", events = LoadEvent.REDEFINE)
public void invalidateClassCache() throws Exception {
if (!regBeanMetaDataManagers.isEmpty() || !regAnnotatedMetaDataProviders.isEmpty()) {
scheduler.scheduleCommand(invalidateHibernateValidatorCaches);
}
}
// reload the configuration - schedule a command to run in the application classloader and merge
// duplicate commands.
private void refresh(int timeout) {
if (hibernateEjb)
scheduler.scheduleCommand(reloadEntityManagerFactoryCommand, timeout);
else
scheduler.scheduleCommand(reloadSessionFactoryCommand, timeout);
}
public void registerAnnotationMetaDataProvider(Object annotatedMetaDataProvider) {
regAnnotatedMetaDataProviders.add(annotatedMetaDataProvider);
}
public void registerBeanMetaDataManager(Object beanMetaDataManager) {
regBeanMetaDataManagers.add(beanMetaDataManager);
}
private Class<?> resolveClass(String name) throws ClassNotFoundException {
return Class.forName(name, true, appClassLoader);
}
}