package org.hotswap.agent.plugin.hibernate.proxy; import org.hibernate.Version; import org.hotswap.agent.javassist.compiler.ast.StringL; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.util.ReflectionHelper; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.spi.PersistenceUnitInfo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; /** * Create a proxy for EntityManagerFactory and register all created proxies. * Provide static method to reload a proxied factory. * <p/> * This class must run in App classloader. * * @author Jiri Bubnik */ public class EntityManagerFactoryProxy { private static AgentLogger LOGGER = AgentLogger.getLogger(EntityManagerFactoryProxy.class); // Map persistenceUnitName -> Wrapper instance private static Map<String, EntityManagerFactoryProxy> proxiedFactories = new HashMap<String, EntityManagerFactoryProxy>(); // hold lock during refresh. The lock is checked on each factory method call. final Object reloadLock = new Object(); // current entity manager factory instance - this is the target this proxy delegates to EntityManagerFactory currentInstance; // info and properties to use to build fresh instance of factory String persistenceUnitName; PersistenceUnitInfo info; Map properties; // builder object to create properties Object builder; /** * Create new wrapper for persistenceUnitName and hold it's instance for future use. * * @param persistenceUnitName key to the wrapper * @return existing wrapper or new instance (never null) */ public static EntityManagerFactoryProxy getWrapper(String persistenceUnitName) { if (!proxiedFactories.containsKey(persistenceUnitName)) { proxiedFactories.put(persistenceUnitName, new EntityManagerFactoryProxy()); } return proxiedFactories.get(persistenceUnitName); } /** * Refresh all known wrapped factories. */ public static void refreshProxiedFactories() { String[] version = Version.getVersionString().split("\\."); boolean version43OrGreater = false; try { version43OrGreater = Integer.valueOf(version[0]) >= 5 || (Integer.valueOf(version[0]) == 4 && Integer.valueOf(version[1]) >= 3); } catch (Exception e) { LOGGER.warning("Unable to resolve hibernate version '{}'", version); } for (EntityManagerFactoryProxy wrapper : proxiedFactories.values()) { String persistenceClassName = wrapper.properties == null ? null : (String) wrapper.properties.get("PERSISTENCE_CLASS_NAME"); try { // lock proxy execution during reload synchronized (wrapper.reloadLock) { if ("org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider".equals(persistenceClassName)) { wrapper.refreshProxiedFactorySpring(); } else if (version43OrGreater) { wrapper.refreshProxiedFactoryVersion43OrGreater(); } else { wrapper.refreshProxiedFactory(); } } } catch (Exception e) { e.printStackTrace(); } } } private void refreshProxiedFactorySpring() { try { currentInstance = (EntityManagerFactory) ReflectionHelper.invoke(builder, builder.getClass(), "createContainerEntityManagerFactory", new Class[]{PersistenceUnitInfo.class, Map.class}, info, properties); } catch (Exception e) { e.printStackTrace(); LOGGER.error("Unable to reload persistence unit {}", info, e); } } public void refreshProxiedFactoryVersion43OrGreater() { if (info == null) { currentInstance = Persistence.createEntityManagerFactory(persistenceUnitName, properties); } else { try { Class bootstrapClazz = loadClass("org.hibernate.jpa.boot.spi.Bootstrap"); Class builderClazz = loadClass("org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder"); Object builder = ReflectionHelper.invoke(null, bootstrapClazz, "getEntityManagerFactoryBuilder", new Class[]{PersistenceUnitInfo.class, Map.class}, info, properties); currentInstance = (EntityManagerFactory) ReflectionHelper.invoke(builder, builderClazz, "build", new Class[]{}); } catch (Exception e) { e.printStackTrace(); LOGGER.error("Unable to reload persistence unit {}", info, e); } } } /** * Refresh a single persistence unit - replace the wrapped EntityManagerFactory with fresh instance. */ public void refreshProxiedFactory() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { // refresh registry try { Class entityManagerFactoryRegistryClazz = loadClass("org.hibernate.ejb.internal.EntityManagerFactoryRegistry"); Object instance = ReflectionHelper.get(null, entityManagerFactoryRegistryClazz, "INSTANCE"); ReflectionHelper.invoke(instance, entityManagerFactoryRegistryClazz, "removeEntityManagerFactory", new Class[] {String.class, EntityManagerFactory.class}, persistenceUnitName, currentInstance); } catch (Exception e) { LOGGER.error("Unable to clear previous instance of entity manager factory"); } buildFreshEntityManagerFactory(); } // create factory from cached configuration // from HibernatePersistence.createContainerEntityManagerFactory() private void buildFreshEntityManagerFactory() { try { Class ejb3ConfigurationClazz = loadClass("org.hibernate.ejb.Ejb3Configuration"); LOGGER.trace("new Ejb3Configuration()"); Object cfg = ejb3ConfigurationClazz.newInstance(); LOGGER.trace("cfg.configure( info, properties );"); if (info != null) { ReflectionHelper.invoke(cfg, ejb3ConfigurationClazz, "configure", new Class[]{PersistenceUnitInfo.class, Map.class}, info, properties); } else { ReflectionHelper.invoke(cfg, ejb3ConfigurationClazz, "configure", new Class[]{String.class, Map.class}, persistenceUnitName, properties); } LOGGER.trace("configured.buildEntityManagerFactory()"); currentInstance = (EntityManagerFactory) ReflectionHelper.invoke(cfg, ejb3ConfigurationClazz, "buildEntityManagerFactory", new Class[]{}); } catch (Exception e) { LOGGER.error("Unable to build fresh entity manager factory for persistence unit {}", persistenceUnitName); } } /** * Create a proxy for EntityManagerFactory. * * @param factory initial factory to delegate method calls to. * @param info definition to cache for factory reload * @param properties properties to cache for factory reload * @return the proxy */ public EntityManagerFactory proxy(Object builder, EntityManagerFactory factory, String persistenceUnitName, PersistenceUnitInfo info, Map properties) { this.builder = builder; this.currentInstance = factory; this.persistenceUnitName = persistenceUnitName; this.info = info; this.properties = properties; return (EntityManagerFactory) Proxy.newProxyInstance( currentInstance.getClass().getClassLoader(), currentInstance.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // if reload in progress, wait for it synchronized (reloadLock) {} return method.invoke(currentInstance, args); } }); } private Class loadClass(String name) throws ClassNotFoundException { return getClass().getClassLoader().loadClass(name); } }