001: /*
002: * Copyright 2002-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.test.jpa;
018:
019: import java.lang.instrument.ClassFileTransformer;
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.Field;
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024: import java.util.HashMap;
025: import java.util.Map;
026:
027: import javax.persistence.EntityManager;
028: import javax.persistence.EntityManagerFactory;
029:
030: import junit.framework.TestCase;
031:
032: import org.springframework.beans.BeanUtils;
033: import org.springframework.beans.BeansException;
034: import org.springframework.beans.factory.config.BeanPostProcessor;
035: import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
036: import org.springframework.beans.factory.support.BeanDefinitionRegistry;
037: import org.springframework.beans.factory.support.DefaultListableBeanFactory;
038: import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
039: import org.springframework.context.ConfigurableApplicationContext;
040: import org.springframework.context.support.GenericApplicationContext;
041: import org.springframework.instrument.classloading.LoadTimeWeaver;
042: import org.springframework.instrument.classloading.ResourceOverridingShadowingClassLoader;
043: import org.springframework.instrument.classloading.ShadowingClassLoader;
044: import org.springframework.orm.jpa.ExtendedEntityManagerCreator;
045: import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
046: import org.springframework.orm.jpa.SharedEntityManagerCreator;
047: import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
048: import org.springframework.test.annotation.AbstractAnnotationAwareTransactionalTests;
049: import org.springframework.util.StringUtils;
050:
051: /**
052: * Convenient support class for JPA-related tests. Offers the same contract as
053: * AbstractTransactionalDataSourceSpringContextTests and equally good performance,
054: * even when performing the instrumentation required by the JPA specification.
055: *
056: * <p>Exposes an EntityManagerFactory and a shared EntityManager.
057: * Requires an EntityManagerFactory to be injected, plus the DataSource and
058: * JpaTransactionManager through the superclass.
059: *
060: * <p>When using Xerces, make sure a post 2.0.2 version is available on the classpath
061: * to avoid a critical
062: * <a href="http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16014"/>bug</a>
063: * that leads to StackOverflow. Maven users are likely to encounter this problem since
064: * 2.0.2 is used by default.
065: * <p/>
066: * A workaround is to explicitly specify the Xerces version inside the Maven pom:
067: * <pre>
068: * <dependency>
069: * <groupId>xerces</groupId>
070: * <artifactId>xercesImpl</artifactId>
071: * <version>2.8.1</version>
072: * </dependency>
073: * </pre>
074: *
075: * @author Rod Johnson
076: * @author Rob Harrop
077: * @since 2.0
078: */
079: public abstract class AbstractJpaTests extends
080: AbstractAnnotationAwareTransactionalTests {
081:
082: private static final String DEFAULT_ORM_XML_LOCATION = "META-INF/orm.xml";
083:
084: /**
085: * Map from String defining unique combination of config locations, to ApplicationContext.
086: * Values are intentionally not strongly typed, to avoid potential class cast exceptions
087: * through use between different class loaders.
088: */
089: private static Map<String, Object> contextCache = new HashMap<String, Object>();
090:
091: private static Map<String, ClassLoader> classLoaderCache = new HashMap<String, ClassLoader>();
092:
093: protected EntityManagerFactory entityManagerFactory;
094:
095: /**
096: * If this instance is in a shadow loader, this variable
097: * will contain the parent instance of the subclass.
098: * The class will not be the same as the class of the
099: * shadow instance, as it was loaded by a different class loader,
100: * but it can be invoked reflectively. The shadowParent
101: * and the shadow loader can communicate reflectively
102: * but not through direct invocation.
103: */
104: private Object shadowParent;
105:
106: /**
107: * Subclasses can use this in test cases.
108: * It will participate in any current transaction.
109: */
110: protected EntityManager sharedEntityManager;
111:
112: public void setEntityManagerFactory(
113: EntityManagerFactory entityManagerFactory) {
114: this .entityManagerFactory = entityManagerFactory;
115: this .sharedEntityManager = SharedEntityManagerCreator
116: .createSharedEntityManager(this .entityManagerFactory);
117: }
118:
119: /**
120: * Create an EntityManager that will always automatically enlist itself in current
121: * transactions, in contrast to an EntityManager returned by
122: * <code>EntityManagerFactory.createEntityManager()</code>
123: * (which requires an explicit <code>joinTransaction()</code> call).
124: */
125: protected EntityManager createContainerManagedEntityManager() {
126: return ExtendedEntityManagerCreator
127: .createContainerManagedEntityManager(this .entityManagerFactory);
128: }
129:
130: /**
131: * Subclasses should override this method if they wish
132: * to disable shadow class loading. Do this only
133: * if instrumentation is not required in your
134: * JPA implementation.
135: * @return whether to disable shadow loading functionality
136: */
137: protected boolean shouldUseShadowLoader() {
138: return true;
139: }
140:
141: @Override
142: public void setDirty() {
143: super .setDirty();
144: contextCache.remove(cacheKeys());
145: classLoaderCache.remove(cacheKeys());
146:
147: // If we are a shadow loader, we need to invoke
148: // the shadow parent to set it dirty, as
149: // it is the shadow parent that maintains the cache state,
150: // not the child
151: if (this .shadowParent != null) {
152: try {
153: Method m = shadowParent.getClass().getMethod(
154: "setDirty", (Class[]) null);
155: m.invoke(shadowParent, (Object[]) null);
156: } catch (Exception ex) {
157: throw new RuntimeException(ex);
158: }
159: }
160: }
161:
162: @Override
163: public void runBare() throws Throwable {
164: if (!shouldUseShadowLoader()) {
165: super .runBare();
166: return;
167: }
168:
169: String combinationOfContextLocationsForThisTestClass = cacheKeys();
170: ClassLoader classLoaderForThisTestClass = getClass()
171: .getClassLoader();
172: // save the TCCL
173: ClassLoader initialClassLoader = Thread.currentThread()
174: .getContextClassLoader();
175:
176: if (this .shadowParent != null) {
177: Thread.currentThread().setContextClassLoader(
178: classLoaderForThisTestClass);
179: super .runBare();
180: } else {
181: ShadowingClassLoader shadowingClassLoader = (ShadowingClassLoader) classLoaderCache
182: .get(combinationOfContextLocationsForThisTestClass);
183:
184: if (shadowingClassLoader == null) {
185: shadowingClassLoader = (ShadowingClassLoader) createShadowingClassLoader(classLoaderForThisTestClass);
186: classLoaderCache.put(
187: combinationOfContextLocationsForThisTestClass,
188: shadowingClassLoader);
189: }
190: try {
191: Thread.currentThread().setContextClassLoader(
192: shadowingClassLoader);
193: String[] configLocations = getConfigLocations();
194:
195: // Do not strongly type, to avoid ClassCastException.
196: Object cachedContext = contextCache
197: .get(combinationOfContextLocationsForThisTestClass);
198:
199: if (cachedContext == null) {
200:
201: // Create the LoadTimeWeaver.
202: Class shadowingLoadTimeWeaverClass = shadowingClassLoader
203: .loadClass(ShadowingLoadTimeWeaver.class
204: .getName());
205: Constructor constructor = shadowingLoadTimeWeaverClass
206: .getConstructor(ClassLoader.class);
207: constructor.setAccessible(true);
208: Object ltw = constructor
209: .newInstance(shadowingClassLoader);
210:
211: // Create the BeanFactory.
212: Class beanFactoryClass = shadowingClassLoader
213: .loadClass(DefaultListableBeanFactory.class
214: .getName());
215: Object beanFactory = BeanUtils
216: .instantiateClass(beanFactoryClass);
217:
218: // Create the BeanDefinitionReader.
219: Class beanDefinitionReaderClass = shadowingClassLoader
220: .loadClass(XmlBeanDefinitionReader.class
221: .getName());
222: Class beanDefinitionRegistryClass = shadowingClassLoader
223: .loadClass(BeanDefinitionRegistry.class
224: .getName());
225: Object reader = beanDefinitionReaderClass
226: .getConstructor(beanDefinitionRegistryClass)
227: .newInstance(beanFactory);
228:
229: // Load the bean definitions into the BeanFactory.
230: Method loadBeanDefinitions = beanDefinitionReaderClass
231: .getMethod("loadBeanDefinitions",
232: String[].class);
233: loadBeanDefinitions.invoke(reader,
234: new Object[] { configLocations });
235:
236: // Create LoadTimeWeaver-injecting BeanPostProcessor.
237: Class loadTimeWeaverInjectingBeanPostProcessorClass = shadowingClassLoader
238: .loadClass(LoadTimeWeaverInjectingBeanPostProcessor.class
239: .getName());
240: Class loadTimeWeaverClass = shadowingClassLoader
241: .loadClass(LoadTimeWeaver.class.getName());
242: Constructor bppConstructor = loadTimeWeaverInjectingBeanPostProcessorClass
243: .getConstructor(loadTimeWeaverClass);
244: bppConstructor.setAccessible(true);
245: Object beanPostProcessor = bppConstructor
246: .newInstance(ltw);
247:
248: // Add LoadTimeWeaver-injecting BeanPostProcessor.
249: Class beanPostProcessorClass = shadowingClassLoader
250: .loadClass(BeanPostProcessor.class
251: .getName());
252: Method addBeanPostProcessor = beanFactoryClass
253: .getMethod("addBeanPostProcessor",
254: beanPostProcessorClass);
255: addBeanPostProcessor.invoke(beanFactory,
256: beanPostProcessor);
257:
258: // Create the GenericApplicationContext.
259: Class genericApplicationContextClass = shadowingClassLoader
260: .loadClass(GenericApplicationContext.class
261: .getName());
262: Class defaultListableBeanFactoryClass = shadowingClassLoader
263: .loadClass(DefaultListableBeanFactory.class
264: .getName());
265: cachedContext = genericApplicationContextClass
266: .getConstructor(
267: defaultListableBeanFactoryClass)
268: .newInstance(beanFactory);
269:
270: // Invoke the context's "refresh" method.
271: genericApplicationContextClass.getMethod("refresh")
272: .invoke(cachedContext);
273:
274: // Store the context reference in the cache.
275: contextCache
276: .put(
277: combinationOfContextLocationsForThisTestClass,
278: cachedContext);
279: }
280: // create the shadowed test
281: Class shadowedTestClass = shadowingClassLoader
282: .loadClass(getClass().getName());
283:
284: // So long as JUnit is excluded from shadowing we
285: // can minimize reflective invocation here
286: TestCase shadowedTestCase = (TestCase) BeanUtils
287: .instantiateClass(shadowedTestClass);
288:
289: /* shadowParent = this */
290: Class this ShadowedClass = shadowingClassLoader
291: .loadClass(AbstractJpaTests.class.getName());
292: Field shadowed = this ShadowedClass
293: .getDeclaredField("shadowParent");
294: shadowed.setAccessible(true);
295: shadowed.set(shadowedTestCase, this );
296:
297: /* AbstractSpringContextTests.addContext(Object, ApplicationContext) */
298: Class applicationContextClass = shadowingClassLoader
299: .loadClass(ConfigurableApplicationContext.class
300: .getName());
301: Method addContextMethod = shadowedTestClass.getMethod(
302: "addContext", Object.class,
303: applicationContextClass);
304: addContextMethod.invoke(shadowedTestCase,
305: configLocations, cachedContext);
306:
307: // Invoke tests on shadowed test case
308: shadowedTestCase.setName(getName());
309: shadowedTestCase.runBare();
310: } catch (InvocationTargetException ex) {
311: // Unwrap this for better exception reporting
312: // when running tests
313: throw ex.getTargetException();
314: } finally {
315: Thread.currentThread().setContextClassLoader(
316: initialClassLoader);
317: }
318: }
319: }
320:
321: protected String cacheKeys() {
322: return StringUtils
323: .arrayToCommaDelimitedString(getConfigLocations());
324: }
325:
326: /**
327: * NB: This method must <b>not</b> have a return type of ShadowingClassLoader as that would cause that
328: * class to be loaded eagerly when this test case loads, creating verify errors at runtime.
329: */
330: protected ClassLoader createShadowingClassLoader(
331: ClassLoader classLoader) {
332: OrmXmlOverridingShadowingClassLoader orxl = new OrmXmlOverridingShadowingClassLoader(
333: classLoader, getActualOrmXmlLocation());
334: customizeResourceOverridingShadowingClassLoader(orxl);
335: return orxl;
336: }
337:
338: /**
339: * Customize the shadowing class loader.
340: * @param shadowingClassLoader this parameter is actually of type
341: * ResourceOverridingShadowingClassLoader, and can safely to be cast to
342: * that type. However, the signature must not be of that type as that
343: * would cause the present class loader to load that type.
344: */
345: protected void customizeResourceOverridingShadowingClassLoader(
346: ClassLoader shadowingClassLoader) {
347: // empty
348: }
349:
350: /**
351: * Subclasses can override this to return the real location path for
352: * orm.xml or null if they do not wish to find any orm.xml
353: * @return orm.xml path or null to hide any such file
354: */
355: protected String getActualOrmXmlLocation() {
356: return DEFAULT_ORM_XML_LOCATION;
357: }
358:
359: private static class LoadTimeWeaverInjectingBeanPostProcessor
360: extends InstantiationAwareBeanPostProcessorAdapter {
361:
362: private final LoadTimeWeaver ltw;
363:
364: public LoadTimeWeaverInjectingBeanPostProcessor(
365: LoadTimeWeaver ltw) {
366: this .ltw = ltw;
367: }
368:
369: public Object postProcessBeforeInitialization(Object bean,
370: String beanName) throws BeansException {
371: if (bean instanceof LocalContainerEntityManagerFactoryBean) {
372: ((LocalContainerEntityManagerFactoryBean) bean)
373: .setLoadTimeWeaver(this .ltw);
374: }
375: if (bean instanceof DefaultPersistenceUnitManager) {
376: ((DefaultPersistenceUnitManager) bean)
377: .setLoadTimeWeaver(this .ltw);
378: }
379: return bean;
380: }
381: }
382:
383: private static class ShadowingLoadTimeWeaver implements
384: LoadTimeWeaver {
385:
386: private final ClassLoader shadowingClassLoader;
387:
388: private final Class shadowingClassLoaderClass;
389:
390: public ShadowingLoadTimeWeaver(ClassLoader shadowingClassLoader) {
391: this .shadowingClassLoader = shadowingClassLoader;
392: this .shadowingClassLoaderClass = shadowingClassLoader
393: .getClass();
394: }
395:
396: public ClassLoader getInstrumentableClassLoader() {
397: return (ClassLoader) this .shadowingClassLoader;
398: }
399:
400: public ClassLoader getThrowawayClassLoader() {
401: // Be sure to copy the same resource overrides
402: // and same class file transformers:
403: // We want the throwaway class loader to behave
404: // like the instrumentable class loader
405: ResourceOverridingShadowingClassLoader roscl = new ResourceOverridingShadowingClassLoader(
406: getClass().getClassLoader());
407: if (shadowingClassLoader instanceof ResourceOverridingShadowingClassLoader) {
408: roscl
409: .copyOverrides((ResourceOverridingShadowingClassLoader) shadowingClassLoader);
410: }
411: if (shadowingClassLoader instanceof ShadowingClassLoader) {
412: roscl
413: .copyTransformers((ShadowingClassLoader) shadowingClassLoader);
414: }
415: return roscl;
416: }
417:
418: public void addTransformer(ClassFileTransformer transformer) {
419: try {
420: Method addClassFileTransformer = this .shadowingClassLoaderClass
421: .getMethod("addTransformer",
422: ClassFileTransformer.class);
423: addClassFileTransformer.setAccessible(true);
424: addClassFileTransformer.invoke(
425: this .shadowingClassLoader, transformer);
426: } catch (Exception ex) {
427: throw new RuntimeException(ex);
428: }
429: }
430: }
431:
432: }
|