/* * 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.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import javax.persistence.spi.PersistenceUnitInfo; import org.hibernate.Version; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.util.ReflectionHelper; /** * 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 { /** The logger. */ private static AgentLogger LOGGER = AgentLogger.getLogger(EntityManagerFactoryProxy.class); /** The proxied factories. */ // Map persistenceUnitName -> Wrapper instance private static Map<String, EntityManagerFactoryProxy> proxiedFactories = new HashMap<String, EntityManagerFactoryProxy>(); /** The reload lock. */ // hold lock during refresh. The lock is checked on each factory method call. final Object reloadLock = new Object(); /** The current instance. */ // current entity manager factory instance - this is the target this proxy delegates to EntityManagerFactory currentInstance; /** The persistence unit name. */ // info and properties to use to build fresh instance of factory String persistenceUnitName; /** The info. */ PersistenceUnitInfo info; /** The properties. */ Map<?,?> properties; /** * 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]) >= 4 && Integer.valueOf(version[1]) >= 3; } catch (Exception e) { LOGGER.warning("Unable to resolve hibernate version '{}'", Arrays.toString(version)); } for (EntityManagerFactoryProxy wrapper : proxiedFactories.values()) try { // lock proxy execution during reload synchronized (wrapper.reloadLock) { if (version43OrGreater) { wrapper.refreshProxiedFactoryVersion43OrGreater(); } else { wrapper.refreshProxiedFactory(); } } } catch (Exception e) { e.printStackTrace(); } } /** * Refresh proxied factory version43 or greater. */ 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. * * @throws NoSuchMethodException the no such method exception * @throws InvocationTargetException the invocation target exception * @throws IllegalAccessException the illegal access exception * @throws NoSuchFieldException the no such field exception */ 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 /** * Builds the fresh entity manager factory. */ // 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 persistenceUnitName the persistence unit name * @param info definition to cache for factory reload * @param properties properties to cache for factory reload * @return the proxy */ public EntityManagerFactory proxy(EntityManagerFactory factory, String persistenceUnitName, PersistenceUnitInfo info, Map<?,?> properties) { 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); } }); } /** * Load class. * * @param name the name * @return the class * @throws ClassNotFoundException the class not found exception */ private Class<?> loadClass(String name) throws ClassNotFoundException { return getClass().getClassLoader().loadClass(name); } }