/* * 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); } }