/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hotswap.agent.plugin.hibernate3.jpa;
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.Maven;
import org.hotswap.agent.annotation.OnClassFileEvent;
import org.hotswap.agent.annotation.OnClassLoadEvent;
import org.hotswap.agent.annotation.OnResourceFileEvent;
import org.hotswap.agent.annotation.Plugin;
import org.hotswap.agent.annotation.Versions;
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.plugin.hibernate3.jpa.Hibernate3JPARefreshCommands;
import org.hotswap.agent.util.AnnotationHelper;
/**
* Reload Hibernate configuration after entity create/change.
*
* <b>This plugin requires Hibernate3 plugin to be enabled also to work!!</b>
*
* @author Jiri Bubnik
* @author alpapad@gmail.com
*/
@Plugin(name = "Hibernate3JPA", //
group = "groupHibernate",
description = "Reload Hibernate configuration after entity create/change.", //
testedVersions = { "3.6" }, //
expectedVersions = { "3.6" }, //
supportClass = { Hibernate3JPATransformers.class })
@Versions(maven = { @Maven(value = "[3.0,4.0)", artifactId = "hibernate-entitymanager", groupId = "org.hibernate") })
public class Hibernate3JPAPlugin {
/** The Constant ENTITY_ANNOTATION. */
private static final String ENTITY_ANNOTATION = "javax.persistence.Entity";
/** The logger. */
private static AgentLogger LOGGER = AgentLogger.getLogger(Hibernate3JPAPlugin.class);
/** The scheduler. */
@Init
Scheduler scheduler;
/** The app class loader. */
@Init
ClassLoader appClassLoader;
/** The reg annotated meta data providers. */
Set<Object> regAnnotatedMetaDataProviders = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
/** The reg bean meta data managers. */
Set<Object> regBeanMetaDataManagers = Collections.newSetFromMap(new WeakHashMap<Object, Boolean>());
/** The reload entity manager factory command. */
// refresh commands
Command reloadEntityManagerFactoryCommand = new ReflectionCommand(this, Hibernate3JPARefreshCommands.class.getName(),
"reloadEntityManagerFactory");
/** The invalidate hibernate validator caches. */
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);
}
}
};
/** The hibernate ejb. */
// is EJB3 or plain hibernate
boolean hibernateEjb;
/**
* Plugin initialization properties (from Hibernate3JPAHelper or
* SessionFactoryProxy).
*
* @param version the version
* @param hibernateEjb the hibernate ejb
*/
public void init(String version, Boolean hibernateEjb) {
LOGGER.info("Hibernate3 JPA plugin initialized - Hibernate Core version '{}'", version);
this.hibernateEjb = hibernateEjb;
}
/**
* Reload after entity class change. It covers also @Entity annotation
* removal.
*
* @param clazz the clazz
* @param original the original
*/
@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.
*
* @param clazz the clazz
* @throws Exception the exception
*/
@OnClassFileEvent(classNameRegexp = ".*", events = { FileEvent.CREATE })
public void newEntity(CtClass clazz) throws Exception {
if (AnnotationHelper.hasAnnotation(clazz, ENTITY_ANNOTATION)) {
refresh(500);
}
}
/**
* Reload on hbm file modifications.
*/
@OnResourceFileEvent(path = "/", filter = ".*.hbm.xml")
public void refreshOnHbm(){
refresh(500);
}
/**
* Reload on hibernate.cfg.xml file modifications
*/
@OnResourceFileEvent(path = "/", filter = ".*.cfg.xml")
public void refreshOnCfg(){
refresh(500);
}
/**
* Reload on persistence.xml file modifications
*/
@OnResourceFileEvent(path = "/", filter = "persistence.xml")
public void refreshOnPersistenceXml(){
refresh(500);
}
/**
* Invalidate class cache.
*
* @throws Exception the exception
*/
@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
/**
* Refresh.
*
* @param timeout the timeout
*/
// duplicate commands.
private void refresh(int timeout) {
scheduler.scheduleCommand(reloadEntityManagerFactoryCommand, timeout);
}
/**
* Register annotation meta data provider.
*
* @param annotatedMetaDataProvider the annotated meta data provider
*/
public void registerAnnotationMetaDataProvider(Object annotatedMetaDataProvider) {
regAnnotatedMetaDataProviders.add(annotatedMetaDataProvider);
}
/**
* Register bean meta data manager.
*
* @param beanMetaDataManager the bean meta data manager
*/
public void registerBeanMetaDataManager(Object beanMetaDataManager) {
regBeanMetaDataManagers.add(beanMetaDataManager);
}
/**
* Resolve class.
*
* @param name the name
* @return the class
* @throws ClassNotFoundException the class not found exception
*/
private Class<?> resolveClass(String name) throws ClassNotFoundException {
return Class.forName(name, true, appClassLoader);
}
}