All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.test.jpa.AbstractJpaTests Maven / Gradle / Ivy

There is a newer version: 5.3.39
Show newest version
/*
 * Copyright 2002-2006 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.springframework.test.jpa;

import java.lang.instrument.ClassFileTransformer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import junit.framework.TestCase;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.instrument.classloading.ResourceOverridingShadowingClassLoader;
import org.springframework.instrument.classloading.ShadowingClassLoader;
import org.springframework.orm.jpa.ExtendedEntityManagerCreator;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.SharedEntityManagerCreator;
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
import org.springframework.test.annotation.AbstractAnnotationAwareTransactionalTests;
import org.springframework.util.StringUtils;

/**
 * Convenient support class for JPA-related tests. Offers the same contract as
 * AbstractTransactionalDataSourceSpringContextTests and equally good performance,
 * even when performing the instrumentation required by the JPA specification.
 *
 * 

Exposes an EntityManagerFactory and a shared EntityManager. * Requires an EntityManagerFactory to be injected, plus the DataSource and * JpaTransactionManager through the superclass. * *

When using Xerces, make sure a post 2.0.2 version is available on the classpath * to avoid a critical * bug * that leads to StackOverflow. Maven users are likely to encounter this problem since * 2.0.2 is used by default. *

* A workaround is to explicitly specify the Xerces version inside the Maven pom: *

 * <dependency>
 *   <groupId>xerces</groupId>
 *     <artifactId>xercesImpl</artifactId>
 *   <version>2.8.1</version>
 * </dependency>
 * 
* * @author Rod Johnson * @author Rob Harrop * @since 2.0 */ public abstract class AbstractJpaTests extends AbstractAnnotationAwareTransactionalTests { private static final String DEFAULT_ORM_XML_LOCATION = "META-INF/orm.xml"; /** * Map from String defining unique combination of config locations, to ApplicationContext. * Values are intentionally not strongly typed, to avoid potential class cast exceptions * through use between different class loaders. */ private static Map contextCache = new HashMap(); private static Map classLoaderCache = new HashMap(); protected EntityManagerFactory entityManagerFactory; /** * If this instance is in a shadow loader, this variable * will contain the parent instance of the subclass. * The class will not be the same as the class of the * shadow instance, as it was loaded by a different class loader, * but it can be invoked reflectively. The shadowParent * and the shadow loader can communicate reflectively * but not through direct invocation. */ private Object shadowParent; /** * Subclasses can use this in test cases. * It will participate in any current transaction. */ protected EntityManager sharedEntityManager; public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; this.sharedEntityManager = SharedEntityManagerCreator.createSharedEntityManager(this.entityManagerFactory); } /** * Create an EntityManager that will always automatically enlist itself in current * transactions, in contrast to an EntityManager returned by * EntityManagerFactory.createEntityManager() * (which requires an explicit joinTransaction() call). */ protected EntityManager createContainerManagedEntityManager() { return ExtendedEntityManagerCreator.createContainerManagedEntityManager(this.entityManagerFactory); } /** * Subclasses should override this method if they wish * to disable shadow class loading. Do this only * if instrumentation is not required in your * JPA implementation. * @return whether to disable shadow loading functionality */ protected boolean shouldUseShadowLoader() { return true; } @Override public void setDirty() { super.setDirty(); contextCache.remove(cacheKeys()); classLoaderCache.remove(cacheKeys()); // If we are a shadow loader, we need to invoke // the shadow parent to set it dirty, as // it is the shadow parent that maintains the cache state, // not the child if (this.shadowParent != null) { try { Method m = shadowParent.getClass().getMethod("setDirty", (Class[]) null); m.invoke(shadowParent, (Object[]) null); } catch (Exception ex) { throw new RuntimeException(ex); } } } @Override public void runBare() throws Throwable { if (!shouldUseShadowLoader()) { super.runBare(); return; } String combinationOfContextLocationsForThisTestClass = cacheKeys(); ClassLoader classLoaderForThisTestClass = getClass().getClassLoader(); // save the TCCL ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader(); if (this.shadowParent != null) { Thread.currentThread().setContextClassLoader(classLoaderForThisTestClass); super.runBare(); } else { ShadowingClassLoader shadowingClassLoader = (ShadowingClassLoader) classLoaderCache.get(combinationOfContextLocationsForThisTestClass); if (shadowingClassLoader == null) { shadowingClassLoader = (ShadowingClassLoader) createShadowingClassLoader(classLoaderForThisTestClass); classLoaderCache.put(combinationOfContextLocationsForThisTestClass, shadowingClassLoader); } try { Thread.currentThread().setContextClassLoader(shadowingClassLoader); String[] configLocations = getConfigLocations(); // Do not strongly type, to avoid ClassCastException. Object cachedContext = contextCache.get(combinationOfContextLocationsForThisTestClass); if (cachedContext == null) { // Create the LoadTimeWeaver. Class shadowingLoadTimeWeaverClass = shadowingClassLoader.loadClass(ShadowingLoadTimeWeaver.class.getName()); Constructor constructor = shadowingLoadTimeWeaverClass.getConstructor(ClassLoader.class); constructor.setAccessible(true); Object ltw = constructor.newInstance(shadowingClassLoader); // Create the BeanFactory. Class beanFactoryClass = shadowingClassLoader.loadClass(DefaultListableBeanFactory.class.getName()); Object beanFactory = BeanUtils.instantiateClass(beanFactoryClass); // Create the BeanDefinitionReader. Class beanDefinitionReaderClass = shadowingClassLoader.loadClass(XmlBeanDefinitionReader.class.getName()); Class beanDefinitionRegistryClass = shadowingClassLoader.loadClass(BeanDefinitionRegistry.class.getName()); Object reader = beanDefinitionReaderClass.getConstructor(beanDefinitionRegistryClass).newInstance(beanFactory); // Load the bean definitions into the BeanFactory. Method loadBeanDefinitions = beanDefinitionReaderClass.getMethod("loadBeanDefinitions", String[].class); loadBeanDefinitions.invoke(reader, new Object[]{configLocations}); // Create LoadTimeWeaver-injecting BeanPostProcessor. Class loadTimeWeaverInjectingBeanPostProcessorClass = shadowingClassLoader.loadClass(LoadTimeWeaverInjectingBeanPostProcessor.class.getName()); Class loadTimeWeaverClass = shadowingClassLoader.loadClass(LoadTimeWeaver.class.getName()); Constructor bppConstructor = loadTimeWeaverInjectingBeanPostProcessorClass.getConstructor(loadTimeWeaverClass); bppConstructor.setAccessible(true); Object beanPostProcessor = bppConstructor.newInstance(ltw); // Add LoadTimeWeaver-injecting BeanPostProcessor. Class beanPostProcessorClass = shadowingClassLoader.loadClass(BeanPostProcessor.class.getName()); Method addBeanPostProcessor = beanFactoryClass.getMethod("addBeanPostProcessor", beanPostProcessorClass); addBeanPostProcessor.invoke(beanFactory, beanPostProcessor); // Create the GenericApplicationContext. Class genericApplicationContextClass = shadowingClassLoader.loadClass(GenericApplicationContext.class.getName()); Class defaultListableBeanFactoryClass = shadowingClassLoader.loadClass(DefaultListableBeanFactory.class.getName()); cachedContext = genericApplicationContextClass.getConstructor(defaultListableBeanFactoryClass).newInstance(beanFactory); // Invoke the context's "refresh" method. genericApplicationContextClass.getMethod("refresh").invoke(cachedContext); // Store the context reference in the cache. contextCache.put(combinationOfContextLocationsForThisTestClass, cachedContext); } // create the shadowed test Class shadowedTestClass = shadowingClassLoader.loadClass(getClass().getName()); // So long as JUnit is excluded from shadowing we // can minimize reflective invocation here TestCase shadowedTestCase = (TestCase) BeanUtils.instantiateClass(shadowedTestClass); /* shadowParent = this */ Class thisShadowedClass = shadowingClassLoader.loadClass(AbstractJpaTests.class.getName()); Field shadowed = thisShadowedClass.getDeclaredField("shadowParent"); shadowed.setAccessible(true); shadowed.set(shadowedTestCase, this); /* AbstractSpringContextTests.addContext(Object, ApplicationContext) */ Class applicationContextClass = shadowingClassLoader.loadClass(ConfigurableApplicationContext.class.getName()); Method addContextMethod = shadowedTestClass.getMethod("addContext", Object.class, applicationContextClass); addContextMethod.invoke(shadowedTestCase, configLocations, cachedContext); // Invoke tests on shadowed test case shadowedTestCase.setName(getName()); shadowedTestCase.runBare(); } catch (InvocationTargetException ex) { // Unwrap this for better exception reporting // when running tests throw ex.getTargetException(); } finally { Thread.currentThread().setContextClassLoader(initialClassLoader); } } } protected String cacheKeys() { return StringUtils.arrayToCommaDelimitedString(getConfigLocations()); } /** * NB: This method must not have a return type of ShadowingClassLoader as that would cause that * class to be loaded eagerly when this test case loads, creating verify errors at runtime. */ protected ClassLoader createShadowingClassLoader(ClassLoader classLoader) { OrmXmlOverridingShadowingClassLoader orxl = new OrmXmlOverridingShadowingClassLoader(classLoader, getActualOrmXmlLocation()); customizeResourceOverridingShadowingClassLoader(orxl); return orxl; } /** * Customize the shadowing class loader. * @param shadowingClassLoader this parameter is actually of type * ResourceOverridingShadowingClassLoader, and can safely to be cast to * that type. However, the signature must not be of that type as that * would cause the present class loader to load that type. */ protected void customizeResourceOverridingShadowingClassLoader(ClassLoader shadowingClassLoader) { // empty } /** * Subclasses can override this to return the real location path for * orm.xml or null if they do not wish to find any orm.xml * @return orm.xml path or null to hide any such file */ protected String getActualOrmXmlLocation() { return DEFAULT_ORM_XML_LOCATION; } private static class LoadTimeWeaverInjectingBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter { private final LoadTimeWeaver ltw; public LoadTimeWeaverInjectingBeanPostProcessor(LoadTimeWeaver ltw) { this.ltw = ltw; } public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof LocalContainerEntityManagerFactoryBean) { ((LocalContainerEntityManagerFactoryBean) bean).setLoadTimeWeaver(this.ltw); } if (bean instanceof DefaultPersistenceUnitManager) { ((DefaultPersistenceUnitManager) bean).setLoadTimeWeaver(this.ltw); } return bean; } } private static class ShadowingLoadTimeWeaver implements LoadTimeWeaver { private final ClassLoader shadowingClassLoader; private final Class shadowingClassLoaderClass; public ShadowingLoadTimeWeaver(ClassLoader shadowingClassLoader) { this.shadowingClassLoader = shadowingClassLoader; this.shadowingClassLoaderClass = shadowingClassLoader.getClass(); } public ClassLoader getInstrumentableClassLoader() { return (ClassLoader) this.shadowingClassLoader; } public ClassLoader getThrowawayClassLoader() { // Be sure to copy the same resource overrides // and same class file transformers: // We want the throwaway class loader to behave // like the instrumentable class loader ResourceOverridingShadowingClassLoader roscl = new ResourceOverridingShadowingClassLoader(getClass().getClassLoader()); if (shadowingClassLoader instanceof ResourceOverridingShadowingClassLoader) { roscl.copyOverrides((ResourceOverridingShadowingClassLoader) shadowingClassLoader); } if (shadowingClassLoader instanceof ShadowingClassLoader) { roscl.copyTransformers((ShadowingClassLoader) shadowingClassLoader); } return roscl; } public void addTransformer(ClassFileTransformer transformer) { try { Method addClassFileTransformer = this.shadowingClassLoaderClass.getMethod("addTransformer", ClassFileTransformer.class); addClassFileTransformer.setAccessible(true); addClassFileTransformer.invoke(this.shadowingClassLoader, transformer); } catch (Exception ex) { throw new RuntimeException(ex); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy