KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > test > jpa > AbstractJpaTests


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.test.jpa;
18
19 import java.lang.instrument.ClassFileTransformer JavaDoc;
20 import java.lang.reflect.Constructor JavaDoc;
21 import java.lang.reflect.Field JavaDoc;
22 import java.lang.reflect.InvocationTargetException JavaDoc;
23 import java.lang.reflect.Method JavaDoc;
24 import java.util.HashMap JavaDoc;
25 import java.util.Map JavaDoc;
26
27 import javax.persistence.EntityManager;
28 import javax.persistence.EntityManagerFactory;
29
30 import junit.framework.TestCase;
31
32 import org.springframework.beans.BeanUtils;
33 import org.springframework.beans.BeansException;
34 import org.springframework.beans.factory.config.BeanPostProcessor;
35 import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
36 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
37 import org.springframework.beans.factory.support.DefaultListableBeanFactory;
38 import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
39 import org.springframework.context.ConfigurableApplicationContext;
40 import org.springframework.context.support.GenericApplicationContext;
41 import org.springframework.instrument.classloading.LoadTimeWeaver;
42 import org.springframework.instrument.classloading.ResourceOverridingShadowingClassLoader;
43 import org.springframework.instrument.classloading.ShadowingClassLoader;
44 import org.springframework.orm.jpa.ExtendedEntityManagerCreator;
45 import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
46 import org.springframework.orm.jpa.SharedEntityManagerCreator;
47 import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
48 import org.springframework.test.annotation.AbstractAnnotationAwareTransactionalTests;
49 import org.springframework.util.StringUtils;
50
51 /**
52  * Convenient support class for JPA-related tests. Offers the same contract as
53  * AbstractTransactionalDataSourceSpringContextTests and equally good performance,
54  * even when performing the instrumentation required by the JPA specification.
55  *
56  * <p>Exposes an EntityManagerFactory and a shared EntityManager.
57  * Requires an EntityManagerFactory to be injected, plus the DataSource and
58  * JpaTransactionManager through the superclass.
59  *
60  * @author Rod Johnson
61  * @author Rob Harrop
62  * @since 2.0
63  */

64 public abstract class AbstractJpaTests extends AbstractAnnotationAwareTransactionalTests {
65
66     private static final String JavaDoc DEFAULT_ORM_XML_LOCATION = "META-INF/orm.xml";
67     
68     /**
69      * Map from String defining unique combination of config locations, to ApplicationContext.
70      * Values are intentionally not strongly typed, to avoid potential class cast exceptions
71      * through use between different class loaders.
72      */

73     private static Map JavaDoc<String JavaDoc, Object JavaDoc> contextCache = new HashMap JavaDoc<String JavaDoc, Object JavaDoc>();
74
75     private static Map JavaDoc<String JavaDoc, ClassLoader JavaDoc> classLoaderCache = new HashMap JavaDoc<String JavaDoc, ClassLoader JavaDoc>();
76
77     protected EntityManagerFactory entityManagerFactory;
78
79     /**
80      * If this instance is in a shadow loader, this variable
81      * will contain the parent instance of the subclass.
82      * The class will not be the same as the class of the
83      * shadow instance, as it was loaded by a different class loader,
84      * but it can be invoked reflectively. The shadowParent
85      * and the shadow loader can communicate reflectively
86      * but not through direct invocation.
87      */

88     private Object JavaDoc shadowParent;
89
90     /**
91      * Subclasses can use this in test cases.
92      * It will participate in any current transaction.
93      */

94     protected EntityManager sharedEntityManager;
95
96
97     public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
98         this.entityManagerFactory = entityManagerFactory;
99         this.sharedEntityManager = SharedEntityManagerCreator.createSharedEntityManager(this.entityManagerFactory);
100     }
101
102     /**
103      * Create an EntityManager that will always automatically enlist itself in current
104      * transactions, in contrast to an EntityManager returned by
105      * <code>EntityManagerFactory.createEntityManager()</code>
106      * (which requires an explicit <code>joinTransaction()</code> call).
107      */

108     protected EntityManager createContainerManagedEntityManager() {
109         return ExtendedEntityManagerCreator.createContainerManagedEntityManager(this.entityManagerFactory);
110     }
111     
112     /**
113      * Subclasses should override this method if they wish
114      * to disable shadow class loading. Do this only
115      * if instrumentation is not required in your
116      * JPA implementation.
117      * @return whether to disable shadow loading functionality
118      */

119     protected boolean shouldUseShadowLoader() {
120         return true;
121     }
122     
123     @Override JavaDoc
124     public void setDirty() {
125         super.setDirty();
126         contextCache.remove(cacheKeys());
127         classLoaderCache.remove(cacheKeys());
128         
129         // If we are a shadow loader, we need to invoke
130
// the shadow parent to set it dirty, as
131
// it is the shadow parent that maintains the cache state,
132
// not the child
133
if (this.shadowParent != null) {
134             try {
135                 Method JavaDoc m = shadowParent.getClass().getMethod("setDirty", (Class JavaDoc[]) null);
136                 m.invoke(shadowParent, (Object JavaDoc[]) null);
137             }
138             catch (Exception JavaDoc ex) {
139                 throw new RuntimeException JavaDoc(ex);
140             }
141         }
142     }
143
144     
145     @Override JavaDoc
146     public void runBare() throws Throwable JavaDoc {
147         if (!shouldUseShadowLoader()) {
148             super.runBare();
149             return;
150         }
151         
152         String JavaDoc combinationOfContextLocationsForThisTestClass = cacheKeys();
153         ClassLoader JavaDoc classLoaderForThisTestClass = getClass().getClassLoader();
154         // save the TCCL
155
ClassLoader JavaDoc initialClassLoader = Thread.currentThread().getContextClassLoader();
156         
157         if (this.shadowParent != null) {
158             Thread.currentThread().setContextClassLoader(classLoaderForThisTestClass);
159             super.runBare();
160         }
161         else {
162             ShadowingClassLoader shadowingClassLoader = (ShadowingClassLoader) classLoaderCache.get(combinationOfContextLocationsForThisTestClass);
163
164             if (shadowingClassLoader == null) {
165                 shadowingClassLoader = (ShadowingClassLoader) createShadowingClassLoader(classLoaderForThisTestClass);
166                 classLoaderCache.put(combinationOfContextLocationsForThisTestClass, shadowingClassLoader);
167             }
168             try {
169                 Thread.currentThread().setContextClassLoader(shadowingClassLoader);
170                 String JavaDoc[] configLocations = getConfigLocations();
171
172                 // Do not strongly type, to avoid ClassCastException.
173
Object JavaDoc cachedContext = contextCache.get(combinationOfContextLocationsForThisTestClass);
174
175                 if (cachedContext == null) {
176
177                     // Create the LoadTimeWeaver.
178
Class JavaDoc shadowingLoadTimeWeaverClass = shadowingClassLoader.loadClass(ShadowingLoadTimeWeaver.class.getName());
179                     Constructor JavaDoc constructor = shadowingLoadTimeWeaverClass.getConstructor(ClassLoader JavaDoc.class);
180                     constructor.setAccessible(true);
181                     Object JavaDoc ltw = constructor.newInstance(shadowingClassLoader);
182
183                     // Create the BeanFactory.
184
Class JavaDoc beanFactoryClass = shadowingClassLoader.loadClass(DefaultListableBeanFactory.class.getName());
185                     Object JavaDoc beanFactory = BeanUtils.instantiateClass(beanFactoryClass);
186
187                     // Create the BeanDefinitionReader.
188
Class JavaDoc beanDefinitionReaderClass = shadowingClassLoader.loadClass(XmlBeanDefinitionReader.class.getName());
189                     Class JavaDoc beanDefinitionRegistryClass = shadowingClassLoader.loadClass(BeanDefinitionRegistry.class.getName());
190                     Object JavaDoc reader = beanDefinitionReaderClass.getConstructor(beanDefinitionRegistryClass).newInstance(beanFactory);
191
192                     // Load the bean definitions into the BeanFactory.
193
Method JavaDoc loadBeanDefinitions = beanDefinitionReaderClass.getMethod("loadBeanDefinitions", String JavaDoc[].class);
194                     loadBeanDefinitions.invoke(reader, new Object JavaDoc[]{configLocations});
195
196                     // Create LoadTimeWeaver-injecting BeanPostProcessor.
197
Class JavaDoc loadTimeWeaverInjectingBeanPostProcessorClass = shadowingClassLoader.loadClass(LoadTimeWeaverInjectingBeanPostProcessor.class.getName());
198                     Class JavaDoc loadTimeWeaverClass = shadowingClassLoader.loadClass(LoadTimeWeaver.class.getName());
199                     Constructor JavaDoc bppConstructor = loadTimeWeaverInjectingBeanPostProcessorClass.getConstructor(loadTimeWeaverClass);
200                     bppConstructor.setAccessible(true);
201                     Object JavaDoc beanPostProcessor = bppConstructor.newInstance(ltw);
202
203                     // Add LoadTimeWeaver-injecting BeanPostProcessor.
204
Class JavaDoc beanPostProcessorClass = shadowingClassLoader.loadClass(BeanPostProcessor.class.getName());
205                     Method JavaDoc addBeanPostProcessor = beanFactoryClass.getMethod("addBeanPostProcessor", beanPostProcessorClass);
206                     addBeanPostProcessor.invoke(beanFactory, beanPostProcessor);
207
208                     // Create the GenericApplicationContext.
209
Class JavaDoc genericApplicationContextClass = shadowingClassLoader.loadClass(GenericApplicationContext.class.getName());
210                     Class JavaDoc defaultListableBeanFactoryClass = shadowingClassLoader.loadClass(DefaultListableBeanFactory.class.getName());
211                     cachedContext = genericApplicationContextClass.getConstructor(defaultListableBeanFactoryClass).newInstance(beanFactory);
212
213                     // Invoke the context's "refresh" method.
214
genericApplicationContextClass.getMethod("refresh").invoke(cachedContext);
215
216                     // Store the context reference in the cache.
217
contextCache.put(combinationOfContextLocationsForThisTestClass, cachedContext);
218                 }
219                 // create the shadowed test
220
Class JavaDoc shadowedTestClass = shadowingClassLoader.loadClass(getClass().getName());
221                 
222                 // So long as JUnit is excluded from shadowing we
223
// can minimize reflective invocation here
224
TestCase shadowedTestCase = (TestCase) BeanUtils.instantiateClass(shadowedTestClass);
225
226                 /* shadowParent = this */
227                 Class JavaDoc thisShadowedClass = shadowingClassLoader.loadClass(AbstractJpaTests.class.getName());
228                 Field JavaDoc shadowed = thisShadowedClass.getDeclaredField("shadowParent");
229                 shadowed.setAccessible(true);
230                 shadowed.set(shadowedTestCase, this);
231
232                 /* AbstractSpringContextTests.addContext(Object, ApplicationContext) */
233                 Class JavaDoc applicationContextClass = shadowingClassLoader.loadClass(ConfigurableApplicationContext.class.getName());
234                 Method JavaDoc addContextMethod = shadowedTestClass.getMethod("addContext", Object JavaDoc.class, applicationContextClass);
235                 addContextMethod.invoke(shadowedTestCase, configLocations, cachedContext);
236
237                 // Invoke tests on shadowed test case
238
shadowedTestCase.setName(getName());
239                 shadowedTestCase.runBare();
240             }
241             catch (InvocationTargetException JavaDoc ex) {
242                 // Unwrap this for better exception reporting
243
// when running tests
244
throw ex.getTargetException();
245             }
246             finally {
247                 Thread.currentThread().setContextClassLoader(initialClassLoader);
248             }
249         }
250     }
251
252     protected String JavaDoc cacheKeys() {
253         return StringUtils.arrayToCommaDelimitedString(getConfigLocations());
254     }
255
256     /**
257      * NB: This method must <b>not</b> have a return type of ShadowingClassLoader as that would cause that
258      * class to be loaded eagerly when this test case loads, creating verify errors at runtime.
259      */

260     protected ClassLoader JavaDoc createShadowingClassLoader(ClassLoader JavaDoc classLoader) {
261         OrmXmlOverridingShadowingClassLoader orxl = new OrmXmlOverridingShadowingClassLoader(classLoader,
262                 getActualOrmXmlLocation());
263         customizeResourceOverridingShadowingClassLoader(orxl);
264         return orxl;
265     }
266     
267     /**
268      * Customize the shadowing class loader.
269      * @param shadowingClassLoader this parameter is actually of type
270      * ResourceOverridingShadowingClassLoader, and can safely to be cast to
271      * that type. However, the signature must not be of that type as that
272      * would cause the present class loader to load that type.
273      */

274     protected void customizeResourceOverridingShadowingClassLoader(ClassLoader JavaDoc shadowingClassLoader) {
275         // empty
276
}
277     
278     /**
279      * Subclasses can override this to return the real location path for
280      * orm.xml or null if they do not wish to find any orm.xml
281      * @return orm.xml path or null to hide any such file
282      */

283     protected String JavaDoc getActualOrmXmlLocation() {
284         return DEFAULT_ORM_XML_LOCATION;
285     }
286
287
288     private static class LoadTimeWeaverInjectingBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {
289
290         private final LoadTimeWeaver ltw;
291
292         public LoadTimeWeaverInjectingBeanPostProcessor(LoadTimeWeaver ltw) {
293             this.ltw = ltw;
294         }
295
296         public Object JavaDoc postProcessBeforeInitialization(Object JavaDoc bean, String JavaDoc beanName) throws BeansException {
297             if (bean instanceof LocalContainerEntityManagerFactoryBean) {
298                 ((LocalContainerEntityManagerFactoryBean) bean).setLoadTimeWeaver(this.ltw);
299             }
300             if (bean instanceof DefaultPersistenceUnitManager) {
301                 ((DefaultPersistenceUnitManager) bean).setLoadTimeWeaver(this.ltw);
302             }
303             return bean;
304         }
305     }
306
307
308     private static class ShadowingLoadTimeWeaver implements LoadTimeWeaver {
309
310         private final ClassLoader JavaDoc shadowingClassLoader;
311
312         private final Class JavaDoc shadowingClassLoaderClass;
313
314         public ShadowingLoadTimeWeaver(ClassLoader JavaDoc shadowingClassLoader) {
315             this.shadowingClassLoader = shadowingClassLoader;
316             this.shadowingClassLoaderClass = shadowingClassLoader.getClass();
317         }
318
319         public ClassLoader JavaDoc getInstrumentableClassLoader() {
320             return (ClassLoader JavaDoc) this.shadowingClassLoader;
321         }
322         
323         public ClassLoader JavaDoc getThrowawayClassLoader() {
324             // Be sure to copy the same resource overrides
325
// and same class file transformers:
326
// We want the throwaway class loader to behave
327
// like the instrumentable class loader
328
ResourceOverridingShadowingClassLoader roscl = new ResourceOverridingShadowingClassLoader(getClass().getClassLoader());
329             if (shadowingClassLoader instanceof ResourceOverridingShadowingClassLoader) {
330                 roscl.copyOverrides((ResourceOverridingShadowingClassLoader) shadowingClassLoader);
331             }
332             if (shadowingClassLoader instanceof ShadowingClassLoader) {
333                 roscl.copyTransformers((ShadowingClassLoader) shadowingClassLoader);
334             }
335             return roscl;
336         }
337
338         public void addTransformer(ClassFileTransformer JavaDoc transformer) {
339             try {
340                 Method JavaDoc addClassFileTransformer =
341                         this.shadowingClassLoaderClass.getMethod("addTransformer", ClassFileTransformer JavaDoc.class);
342                 addClassFileTransformer.setAccessible(true);
343                 addClassFileTransformer.invoke(this.shadowingClassLoader, transformer);
344             }
345             catch (Exception JavaDoc ex) {
346                 throw new RuntimeException JavaDoc(ex);
347             }
348         }
349     }
350
351 }
352
Popular Tags