diff --git a/org.springframework.orm/.classpath b/org.springframework.orm/.classpath
index c3123ef64f4..9a1172d614f 100644
--- a/org.springframework.orm/.classpath
+++ b/org.springframework.orm/.classpath
@@ -13,23 +13,39 @@
+ * Convenient superclass for JUnit 3.8 based tests depending on a Spring + * context. The test instance itself is populated by Dependency Injection. + *
+ *+ * Really for integration testing, not unit testing. You should not + * normally use the Spring container for unit tests: simply populate your POJOs + * in plain JUnit tests! + *
+ *+ * This supports two modes of populating the test: + *
+ *populateProtectedVariables property to true in
+ * the constructor to switch on Field Injection.
+ * false.
+ */
+ public final void setPopulateProtectedVariables(boolean populateFields) {
+ this.populateProtectedVariables = populateFields;
+ }
+
+ /**
+ * Return whether to populate protected variables of this test case.
+ */
+ public final boolean isPopulateProtectedVariables() {
+ return this.populateProtectedVariables;
+ }
+
+ /**
+ * Set the autowire mode for test properties set by Dependency Injection.
+ * The default is {@link #AUTOWIRE_BY_TYPE}. Can be set to + * {@link #AUTOWIRE_BY_NAME} or {@link #AUTOWIRE_NO} instead. + * @see #AUTOWIRE_BY_TYPE + * @see #AUTOWIRE_BY_NAME + * @see #AUTOWIRE_NO + */ + public final void setAutowireMode(final int autowireMode) { + this.autowireMode = autowireMode; + } + + /** + * Return the autowire mode for test properties set by Dependency Injection. + */ + public final int getAutowireMode() { + return this.autowireMode; + } + + /** + * Set whether or not dependency checking should be performed for test + * properties set by Dependency Injection. + *
The default is true, meaning that tests cannot be run
+ * unless all properties are populated.
+ */
+ public final void setDependencyCheck(final boolean dependencyCheck) {
+ this.dependencyCheck = dependencyCheck;
+ }
+
+ /**
+ * Return whether or not dependency checking should be performed for test
+ * properties set by Dependency Injection.
+ */
+ public final boolean isDependencyCheck() {
+ return this.dependencyCheck;
+ }
+
+ /**
+ * Prepare this test instance, injecting dependencies into its protected
+ * fields and its bean properties.
+ *
Note: if the {@link ApplicationContext} for this test instance has not
+ * been configured (e.g., is null), dependency injection
+ * will naturally not be performed, but an informational
+ * message will be written to the log.
+ * @see #injectDependencies()
+ */
+ protected void prepareTestInstance() throws Exception {
+ if (getApplicationContext() == null) {
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("ApplicationContext has not been configured for test [" + getClass().getName()
+ + "]: dependency injection will NOT be performed.");
+ }
+ }
+ else {
+ injectDependencies();
+ }
+ }
+
+ /**
+ * Inject dependencies into 'this' instance (that is, this test instance).
+ *
The default implementation populates protected variables if the + * {@link #populateProtectedVariables() appropriate flag is set}, else uses + * autowiring if autowiring is switched on (which it is by default). + *
Override this method if you need full control over how dependencies are + * injected into the test instance. + * @throws Exception in case of dependency injection failure + * @throws IllegalStateException if the {@link ApplicationContext} for this + * test instance has not been configured + * @see #populateProtectedVariables() + */ + protected void injectDependencies() throws Exception { + Assert.state(getApplicationContext() != null, + "injectDependencies() called without first configuring an ApplicationContext"); + if (isPopulateProtectedVariables()) { + if (this.managedVariableNames == null) { + initManagedVariableNames(); + } + populateProtectedVariables(); + } + getApplicationContext().getBeanFactory().autowireBeanProperties(this, getAutowireMode(), isDependencyCheck()); + } + + private void initManagedVariableNames() throws IllegalAccessException { + List managedVarNames = new LinkedList(); + Class clazz = getClass(); + do { + Field[] fields = clazz.getDeclaredFields(); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Found " + fields.length + " fields on " + clazz); + } + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + field.setAccessible(true); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Candidate field: " + field); + } + if (isProtectedInstanceField(field)) { + Object oldValue = field.get(this); + if (oldValue == null) { + managedVarNames.add(field.getName()); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Added managed variable '" + field.getName() + "'"); + } + } + else { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Rejected managed variable '" + field.getName() + "'"); + } + } + } + } + clazz = clazz.getSuperclass(); + } while (!clazz.equals(AbstractDependencyInjectionSpringContextTests.class)); + + this.managedVariableNames = (String[]) managedVarNames.toArray(new String[managedVarNames.size()]); + } + + private boolean isProtectedInstanceField(Field field) { + int modifiers = field.getModifiers(); + return !Modifier.isStatic(modifiers) && Modifier.isProtected(modifiers); + } + + private void populateProtectedVariables() throws IllegalAccessException { + for (int i = 0; i < this.managedVariableNames.length; i++) { + String varName = this.managedVariableNames[i]; + Object bean = null; + try { + Field field = findField(getClass(), varName); + bean = getApplicationContext().getBean(varName, field.getType()); + field.setAccessible(true); + field.set(this, bean); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Populated field: " + field); + } + } + catch (NoSuchFieldException ex) { + if (this.logger.isWarnEnabled()) { + this.logger.warn("No field with name '" + varName + "'"); + } + } + catch (NoSuchBeanDefinitionException ex) { + if (this.logger.isWarnEnabled()) { + this.logger.warn("No bean with name '" + varName + "'"); + } + } + } + } + + private Field findField(Class clazz, String name) throws NoSuchFieldException { + try { + return clazz.getDeclaredField(name); + } + catch (NoSuchFieldException ex) { + Class superclass = clazz.getSuperclass(); + if (superclass != AbstractSpringContextTests.class) { + return findField(superclass, name); + } + else { + throw ex; + } + } + } + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/AbstractSingleSpringContextTests.java b/org.springframework.orm/src/test/java/org/springframework/test/AbstractSingleSpringContextTests.java new file mode 100644 index 00000000000..b05f927d4aa --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/AbstractSingleSpringContextTests.java @@ -0,0 +1,362 @@ +/* + * Copyright 2002-2008 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; + +import org.springframework.beans.factory.support.BeanDefinitionReader; +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.util.ClassUtils; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +/** + *
+ * Abstract JUnit 3.8 test class that holds and exposes a single Spring + * {@link org.springframework.context.ApplicationContext ApplicationContext}. + *
+ *+ * This class will cache contexts based on a context key: normally the + * config locations String array describing the Spring resource descriptors + * making up the context. Unless the {@link #setDirty()} method is called by a + * test, the context will not be reloaded, even across different subclasses of + * this test. This is particularly beneficial if your context is slow to + * construct, for example if you are using Hibernate and the time taken to load + * the mappings is an issue. + *
+ *+ * For such standard usage, simply override the {@link #getConfigLocations()} + * method and provide the desired config files. For alternative configuration + * options, see {@link #getConfigPath()} and {@link #getConfigPaths()}. + *
+ *+ * If you don't want to load a standard context from an array of config + * locations, you can override the {@link #contextKey()} method. In conjunction + * with this you typically need to override the {@link #loadContext(Object)} + * method, which by default loads the locations specified in the + * {@link #getConfigLocations()} method. + *
+ *+ * WARNING: When doing integration tests from within Eclipse, only use + * classpath resource URLs. Else, you may see misleading failures when changing + * context locations. + *
+ * + * @author Juergen Hoeller + * @author Rod Johnson + * @author Sam Brannen + * @since 2.0 + * @see #getConfigLocations() + * @see #contextKey() + * @see #loadContext(Object) + * @see #getApplicationContext() + * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework + * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests}) + */ +@Deprecated +public abstract class AbstractSingleSpringContextTests extends AbstractSpringContextTests { + + /** Application context this test will run against */ + protected ConfigurableApplicationContext applicationContext; + + private int loadCount = 0; + + + /** + * Default constructor for AbstractSingleSpringContextTests. + */ + public AbstractSingleSpringContextTests() { + } + + /** + * Constructor for AbstractSingleSpringContextTests with a JUnit name. + * @param name the name of this text fixture + */ + public AbstractSingleSpringContextTests(String name) { + super(name); + } + + /** + * This implementation is final. OverrideonSetUp for custom behavior.
+ * @see #onSetUp()
+ */
+ protected final void setUp() throws Exception {
+ // lazy load, in case getApplicationContext() has not yet been called.
+ if (this.applicationContext == null) {
+ this.applicationContext = getContext(contextKey());
+ }
+ prepareTestInstance();
+ onSetUp();
+ }
+
+ /**
+ * Prepare this test instance, for example populating its fields.
+ * The context has already been loaded at the time of this callback.
+ * The default implementation does nothing.
+ * @throws Exception in case of preparation failure
+ */
+ protected void prepareTestInstance() throws Exception {
+ }
+
+ /**
+ * Subclasses can override this method in place of the setUp()
+ * method, which is final in this class.
+ *
The default implementation does nothing.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onSetUp() throws Exception {
+ }
+
+ /**
+ * Called to say that the "applicationContext" instance variable is dirty
+ * and should be reloaded. We need to do this if a test has modified the
+ * context (for example, by replacing a bean definition).
+ */
+ protected void setDirty() {
+ setDirty(contextKey());
+ }
+
+ /**
+ * This implementation is final. Override onTearDown for
+ * custom behavior.
+ * @see #onTearDown()
+ */
+ protected final void tearDown() throws Exception {
+ onTearDown();
+ }
+
+ /**
+ * Subclasses can override this to add custom behavior on teardown.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onTearDown() throws Exception {
+ }
+
+ /**
+ * Return a key for this context. Default is the config location array as
+ * determined by {@link #getConfigLocations()}.
+ *
If you override this method, you will typically have to override + * {@link #loadContext(Object)} as well, being able to handle the key type + * that this method returns. + * @return the context key + * @see #getConfigLocations() + */ + protected Object contextKey() { + return getConfigLocations(); + } + + /** + * This implementation assumes a key of type String array and loads a + * context from the given locations. + *
If you override {@link #contextKey()}, you will typically have to
+ * override this method as well, being able to handle the key type that
+ * contextKey() returns.
+ * @see #getConfigLocations()
+ */
+ protected ConfigurableApplicationContext loadContext(Object key) throws Exception {
+ return loadContextLocations((String[]) key);
+ }
+
+ /**
+ * Load a Spring ApplicationContext from the given config locations.
+ *
The default implementation creates a standard + * {@link #createApplicationContext GenericApplicationContext}, allowing + * for customizing the internal bean factory through + * {@link #customizeBeanFactory}. + * @param locations the config locations (as Spring resource locations, + * e.g. full classpath locations or any kind of URL) + * @return the corresponding ApplicationContext instance (potentially cached) + * @throws Exception if context loading failed + * @see #createApplicationContext(String[]) + */ + protected ConfigurableApplicationContext loadContextLocations(String[] locations) throws Exception { + ++this.loadCount; + if (this.logger.isInfoEnabled()) { + this.logger.info("Loading context for locations: " + StringUtils.arrayToCommaDelimitedString(locations)); + } + return createApplicationContext(locations); + } + + /** + * Create a Spring {@link ConfigurableApplicationContext} for use by this test. + *
The default implementation creates a standard {@link GenericApplicationContext}
+ * instance, calls the {@link #prepareApplicationContext} prepareApplicationContext}
+ * method and the {@link #customizeBeanFactory customizeBeanFactory} method to allow
+ * for customizing the context and its DefaultListableBeanFactory, populates the
+ * context from the specified config locations through the configured
+ * {@link #createBeanDefinitionReader(GenericApplicationContext) BeanDefinitionReader},
+ * and finally {@link ConfigurableApplicationContext#refresh() refreshes} the context.
+ * @param locations the config locations (as Spring resource locations,
+ * e.g. full classpath locations or any kind of URL)
+ * @return the GenericApplicationContext instance
+ * @see #loadContextLocations(String[])
+ * @see #customizeBeanFactory(DefaultListableBeanFactory)
+ * @see #createBeanDefinitionReader(GenericApplicationContext)
+ */
+ protected ConfigurableApplicationContext createApplicationContext(String[] locations) {
+ GenericApplicationContext context = new GenericApplicationContext();
+ prepareApplicationContext(context);
+ customizeBeanFactory(context.getDefaultListableBeanFactory());
+ createBeanDefinitionReader(context).loadBeanDefinitions(locations);
+ context.refresh();
+ return context;
+ }
+
+ /**
+ * Prepare the GenericApplicationContext used by this test.
+ * Called before bean definitions are read.
+ *
The default implementation is empty. Can be overridden in subclasses to + * customize GenericApplicationContext's standard settings. + * @param context the context for which the BeanDefinitionReader should be created + * @see #createApplicationContext + * @see org.springframework.context.support.GenericApplicationContext#setResourceLoader + * @see org.springframework.context.support.GenericApplicationContext#setId + */ + protected void prepareApplicationContext(GenericApplicationContext context) { + } + + /** + * Customize the internal bean factory of the ApplicationContext used by + * this test. Called before bean definitions are read. + *
The default implementation is empty. Can be overridden in subclasses to + * customize DefaultListableBeanFactory's standard settings. + * @param beanFactory the newly created bean factory for this context + * @see #loadContextLocations + * @see #createApplicationContext + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowBeanDefinitionOverriding + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowEagerClassLoading + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowCircularReferences + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping + */ + protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { + } + + /** + * Factory method for creating new {@link BeanDefinitionReader}s for + * loading bean definitions into the supplied + * {@link GenericApplicationContext context}. + *
The default implementation creates a new {@link XmlBeanDefinitionReader}. + * Can be overridden in subclasses to provide a different + * BeanDefinitionReader implementation. + * @param context the context for which the BeanDefinitionReader should be created + * @return a BeanDefinitionReader for the supplied context + * @see #createApplicationContext(String[]) + * @see BeanDefinitionReader + * @see XmlBeanDefinitionReader + */ + protected BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context) { + return new XmlBeanDefinitionReader(context); + } + + /** + * Subclasses can override this method to return the locations of their + * config files, unless they override {@link #contextKey()} and + * {@link #loadContext(Object)} instead. + *
A plain path will be treated as class path location, e.g.: + * "org/springframework/whatever/foo.xml". Note however that you may prefix + * path locations with standard Spring resource prefixes. Therefore, a + * config location path prefixed with "classpath:" with behave the same as a + * plain path, but a config location such as + * "file:/some/path/path/location/appContext.xml" will be treated as a + * filesystem location. + *
The default implementation builds config locations for the config paths + * specified through {@link #getConfigPaths()}. + * @return an array of config locations + * @see #getConfigPaths() + * @see org.springframework.core.io.ResourceLoader#getResource(String) + */ + protected String[] getConfigLocations() { + String[] paths = getConfigPaths(); + String[] locations = new String[paths.length]; + for (int i = 0; i < paths.length; i++) { + String path = paths[i]; + if (path.startsWith("/")) { + locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path; + } + else { + locations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + + StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(getClass()) + "/" + path); + } + } + return locations; + } + + /** + * Subclasses can override this method to return paths to their config + * files, relative to the concrete test class. + *
A plain path, e.g. "context.xml", will be loaded as classpath resource + * from the same package that the concrete test class is defined in. A path + * starting with a slash is treated as fully qualified class path location, + * e.g.: "/org/springframework/whatever/foo.xml". + *
The default implementation builds an array for the config path specified + * through {@link #getConfigPath()}. + * @return an array of config locations + * @see #getConfigPath() + * @see java.lang.Class#getResource(String) + */ + protected String[] getConfigPaths() { + String path = getConfigPath(); + return (path != null ? new String[] { path } : new String[0]); + } + + /** + * Subclasses can override this method to return a single path to a config + * file, relative to the concrete test class. + *
A plain path, e.g. "context.xml", will be loaded as classpath resource + * from the same package that the concrete test class is defined in. A path + * starting with a slash is treated as fully qualified class path location, + * e.g.: "/org/springframework/whatever/foo.xml". + *
The default implementation simply returns null.
+ * @return an array of config locations
+ * @see #getConfigPath()
+ * @see java.lang.Class#getResource(String)
+ */
+ protected String getConfigPath() {
+ return null;
+ }
+
+ /**
+ * Return the ApplicationContext that this base class manages; may be
+ * null.
+ */
+ public final ConfigurableApplicationContext getApplicationContext() {
+ // lazy load, in case setUp() has not yet been called.
+ if (this.applicationContext == null) {
+ try {
+ this.applicationContext = getContext(contextKey());
+ }
+ catch (Exception e) {
+ // log and continue...
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Caught exception while retrieving the ApplicationContext for test [" +
+ getClass().getName() + "." + getName() + "].", e);
+ }
+ }
+ }
+
+ return this.applicationContext;
+ }
+
+ /**
+ * Return the current number of context load attempts.
+ */
+ public final int getLoadCount() {
+ return this.loadCount;
+ }
+
+}
diff --git a/org.springframework.orm/src/test/java/org/springframework/test/AbstractSpringContextTests.java b/org.springframework.orm/src/test/java/org/springframework/test/AbstractSpringContextTests.java
new file mode 100644
index 00000000000..0b4ae975ee1
--- /dev/null
+++ b/org.springframework.orm/src/test/java/org/springframework/test/AbstractSpringContextTests.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2002-2008 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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ *
+ * Superclass for JUnit 3.8 test cases using Spring + * {@link org.springframework.context.ApplicationContext ApplicationContexts}. + *
+ *+ * Maintains a static cache of contexts by key. This has significant performance + * benefit if initializing the context would take time. While initializing a + * Spring context itself is very quick, some beans in a context, such as a + * LocalSessionFactoryBean for working with Hibernate, may take some time to + * initialize. Hence it often makes sense to do that initializing once. + *
+ *+ * Any ApplicationContext created by this class will be asked to register a JVM + * shutdown hook for itself. Unless the context gets closed early, all context + * instances will be automatically closed on JVM shutdown. This allows for + * freeing external resources held by beans within the context, e.g. temporary + * files. + *
+ *+ * Normally you won't extend this class directly but rather one of its + * subclasses. + *
+ * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @since 1.1.1 + * @see AbstractSingleSpringContextTests + * @see AbstractDependencyInjectionSpringContextTests + * @see AbstractTransactionalSpringContextTests + * @see AbstractTransactionalDataSourceSpringContextTests + * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework + * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests}) + */ +@Deprecated +public abstract class AbstractSpringContextTests extends ConditionalTestCase { + + /** + * Map of context keys returned by subclasses of this class, to Spring + * contexts. This needs to be static, as JUnit tests are destroyed and + * recreated between running individual test methods. + */ + private static MapThis is not meant to be used by subclasses. It is rather exposed for
+ * special test suite environments.
+ * @param key the context key
+ * @param context the ApplicationContext instance
+ */
+ public final void addContext(Object key, ConfigurableApplicationContext context) {
+ Assert.notNull(context, "ApplicationContext must not be null");
+ contextKeyToContextMap.put(contextKeyString(key), context);
+ }
+
+ /**
+ * Return whether there is a cached context for the given key.
+ * @param key the context key
+ */
+ protected final boolean hasCachedContext(Object key) {
+ return contextKeyToContextMap.containsKey(contextKeyString(key));
+ }
+
+ /**
+ * Determine if the supplied context key is empty.
+ *
By default, null values, empty strings, and zero-length
+ * arrays are considered empty.
+ * @param key the context key to check
+ * @return true if the supplied context key is empty
+ */
+ protected boolean isContextKeyEmpty(Object key) {
+ return (key == null) || ((key instanceof String) && !StringUtils.hasText((String) key)) ||
+ ((key instanceof Object[]) && ObjectUtils.isEmpty((Object[]) key));
+ }
+
+ /**
+ * Obtain an ApplicationContext for the given key, potentially cached.
+ * @param key the context key; may be null.
+ * @return the corresponding ApplicationContext instance (potentially cached),
+ * or null if the provided key is empty
+ */
+ protected final ConfigurableApplicationContext getContext(Object key) throws Exception {
+ if (isContextKeyEmpty(key)) {
+ return null;
+ }
+ String keyString = contextKeyString(key);
+ ConfigurableApplicationContext ctx = contextKeyToContextMap.get(keyString);
+ if (ctx == null) {
+ ctx = loadContext(key);
+ ctx.registerShutdownHook();
+ contextKeyToContextMap.put(keyString, ctx);
+ }
+ return ctx;
+ }
+
+ /**
+ * Mark the context with the given key as dirty. This will cause the cached
+ * context to be reloaded before the next test case is executed.
+ *
Call this method only if you change the state of a singleton bean, + * potentially affecting future tests. + */ + protected final void setDirty(Object contextKey) { + String keyString = contextKeyString(contextKey); + ConfigurableApplicationContext ctx = contextKeyToContextMap.remove(keyString); + if (ctx != null) { + ctx.close(); + } + } + + /** + * Subclasses can override this to return a String representation of their + * context key for use in caching and logging. + * @param contextKey the context key + */ + protected String contextKeyString(Object contextKey) { + return ObjectUtils.nullSafeToString(contextKey); + } + + /** + * Load a new ApplicationContext for the given key. + *
To be implemented by subclasses. + * @param key the context key + * @return the corresponding ApplicationContext instance (new) + */ + protected abstract ConfigurableApplicationContext loadContext(Object key) throws Exception; + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.java b/org.springframework.orm/src/test/java/org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.java new file mode 100644 index 00000000000..c40137b1035 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/AbstractTransactionalDataSourceSpringContextTests.java @@ -0,0 +1,199 @@ +/* + * Copyright 2002-2008 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; + +import java.io.IOException; +import java.io.LineNumberReader; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.sql.DataSource; + +import org.springframework.core.io.support.EncodedResource; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.jdbc.JdbcTestUtils; +import org.springframework.util.StringUtils; + +/** + * Subclass of AbstractTransactionalSpringContextTests that adds some convenience + * functionality for JDBC access. Expects a {@link javax.sql.DataSource} bean + * to be defined in the Spring application context. + * + *
This class exposes a {@link org.springframework.jdbc.core.JdbcTemplate}
+ * and provides an easy way to delete from the database in a new transaction.
+ *
+ * @author Rod Johnson
+ * @author Juergen Hoeller
+ * @author Thomas Risberg
+ * @since 1.1.1
+ * @see #setDataSource(javax.sql.DataSource)
+ * @see #getJdbcTemplate()
+ * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
+ * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests})
+ */
+@Deprecated
+public abstract class AbstractTransactionalDataSourceSpringContextTests
+ extends AbstractTransactionalSpringContextTests {
+
+ protected JdbcTemplate jdbcTemplate;
+
+ private String sqlScriptEncoding;
+
+ /**
+ * Did this test delete any tables? If so, we forbid transaction completion,
+ * and only allow rollback.
+ */
+ private boolean zappedTables;
+
+
+ /**
+ * Default constructor for AbstractTransactionalDataSourceSpringContextTests.
+ */
+ public AbstractTransactionalDataSourceSpringContextTests() {
+ }
+
+ /**
+ * Constructor for AbstractTransactionalDataSourceSpringContextTests with a JUnit name.
+ */
+ public AbstractTransactionalDataSourceSpringContextTests(String name) {
+ super(name);
+ }
+
+
+ /**
+ * Setter: DataSource is provided by Dependency Injection.
+ */
+ public void setDataSource(DataSource dataSource) {
+ this.jdbcTemplate = new JdbcTemplate(dataSource);
+ }
+
+ /**
+ * Return the JdbcTemplate that this base class manages.
+ */
+ public final JdbcTemplate getJdbcTemplate() {
+ return this.jdbcTemplate;
+ }
+
+ /**
+ * Specify the encoding for SQL scripts, if different from the platform encoding.
+ * @see #executeSqlScript
+ */
+ public void setSqlScriptEncoding(String sqlScriptEncoding) {
+ this.sqlScriptEncoding = sqlScriptEncoding;
+ }
+
+
+ /**
+ * Convenient method to delete all rows from these tables.
+ * Calling this method will make avoidance of rollback by calling
+ * setComplete() impossible.
+ * @see #setComplete
+ */
+ protected void deleteFromTables(String[] names) {
+ for (int i = 0; i < names.length; i++) {
+ int rowCount = this.jdbcTemplate.update("DELETE FROM " + names[i]);
+ if (logger.isInfoEnabled()) {
+ logger.info("Deleted " + rowCount + " rows from table " + names[i]);
+ }
+ }
+ this.zappedTables = true;
+ }
+
+ /**
+ * Overridden to prevent the transaction committing if a number of tables have been
+ * cleared, as a defensive measure against accidental permanent wiping of a database.
+ * @see org.springframework.test.AbstractTransactionalSpringContextTests#setComplete()
+ */
+ protected final void setComplete() {
+ if (this.zappedTables) {
+ throw new IllegalStateException("Cannot set complete after deleting tables");
+ }
+ super.setComplete();
+ }
+
+ /**
+ * Count the rows in the given table
+ * @param tableName table name to count rows in
+ * @return the number of rows in the table
+ */
+ protected int countRowsInTable(String tableName) {
+ return this.jdbcTemplate.queryForInt("SELECT COUNT(0) FROM " + tableName);
+ }
+
+
+ /**
+ * Execute the given SQL script. Will be rolled back by default,
+ * according to the fate of the current transaction.
+ * @param sqlResourcePath Spring resource path for the SQL script.
+ * Should normally be loaded by classpath.
+ *
Statements should be delimited with a semicolon. If statements are not delimited with + * a semicolon then there should be one statement per line. Statements are allowed to span + * lines only if they are delimited with a semicolon. + *
Do not use this method to execute DDL if you expect rollback. + * @param continueOnError whether or not to continue without throwing + * an exception in the event of an error + * @throws DataAccessException if there is an error executing a statement + * and continueOnError was false + */ + protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException { + if (logger.isInfoEnabled()) { + logger.info("Executing SQL script '" + sqlResourcePath + "'"); + } + + EncodedResource resource = + new EncodedResource(getApplicationContext().getResource(sqlResourcePath), this.sqlScriptEncoding); + long startTime = System.currentTimeMillis(); + List statements = new LinkedList(); + try { + LineNumberReader lnr = new LineNumberReader(resource.getReader()); + String script = JdbcTestUtils.readScript(lnr); + char delimiter = ';'; + if (!JdbcTestUtils.containsSqlScriptDelimiters(script, delimiter)) { + delimiter = '\n'; + } + JdbcTestUtils.splitSqlScript(script, delimiter, statements); + for (Iterator itr = statements.iterator(); itr.hasNext(); ) { + String statement = (String) itr.next(); + try { + int rowsAffected = this.jdbcTemplate.update(statement); + if (logger.isDebugEnabled()) { + logger.debug(rowsAffected + " rows affected by SQL: " + statement); + } + } + catch (DataAccessException ex) { + if (continueOnError) { + if (logger.isWarnEnabled()) { + logger.warn("SQL: " + statement + " failed", ex); + } + } + else { + throw ex; + } + } + } + long elapsedTime = System.currentTimeMillis() - startTime; + logger.info("Done executing SQL scriptBuilder '" + sqlResourcePath + "' in " + elapsedTime + " ms"); + } + catch (IOException ex) { + throw new DataAccessResourceFailureException("Failed to open SQL script '" + sqlResourcePath + "'", ex); + } + } + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/AbstractTransactionalSpringContextTests.java b/org.springframework.orm/src/test/java/org/springframework/test/AbstractTransactionalSpringContextTests.java new file mode 100644 index 00000000000..7e32eabd33e --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/AbstractTransactionalSpringContextTests.java @@ -0,0 +1,359 @@ +/* + * Copyright 2002-2008 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; + +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +/** + * Convenient base class for JUnit 3.8 based tests that should occur in a + * transaction, but normally will roll the transaction back on the completion of + * each test. + * + *
This is useful in a range of circumstances, allowing the following benefits: + *
This class is typically very fast, compared to traditional setup/teardown + * scripts. + * + *
If data should be left in the database, call the {@link #setComplete()} + * method in each test. The {@link #setDefaultRollback "defaultRollback"} + * property, which defaults to "true", determines whether transactions will + * complete by default. + * + *
It is even possible to end the transaction early; for example, to verify lazy + * loading behavior of an O/R mapping tool. (This is a valuable away to avoid + * unexpected errors when testing a web UI, for example.) Simply call the + * {@link #endTransaction()} method. Execution will then occur without a + * transactional context. + * + *
The {@link #startNewTransaction()} method may be called after a call to + * {@link #endTransaction()} if you wish to create a new transaction, quite + * independent of the old transaction. The new transaction's default fate will + * be to roll back, unless {@link #setComplete()} is called again during the + * scope of the new transaction. Any number of transactions may be created and + * ended in this way. The final transaction will automatically be rolled back + * when the test case is torn down. + * + *
Transactional behavior requires a single bean in the context implementing the + * {@link PlatformTransactionManager} interface. This will be set by the + * superclass's Dependency Injection mechanism. If using the superclass's Field + * Injection mechanism, the implementation should be named "transactionManager". + * This mechanism allows the use of the + * {@link AbstractDependencyInjectionSpringContextTests} superclass even when + * there is more than one transaction manager in the context. + * + *
This base class can also be used without transaction management, if no + * PlatformTransactionManager bean is found in the context provided. Be + * careful about using this mode, as it allows the potential to permanently + * modify data. This mode is available only if dependency checking is turned off + * in the {@link AbstractDependencyInjectionSpringContextTests} superclass. The + * non-transactional capability is provided to enable use of the same subclass + * in different environments. + * + * @author Rod Johnson + * @author Juergen Hoeller + * @author Sam Brannen + * @since 1.1.1 + * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework + * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests}) + */ +@Deprecated +public abstract class AbstractTransactionalSpringContextTests extends AbstractDependencyInjectionSpringContextTests { + + /** The transaction manager to use */ + protected PlatformTransactionManager transactionManager; + + /** Should we roll back by default? */ + private boolean defaultRollback = true; + + /** Should we commit the current transaction? */ + private boolean complete = false; + + /** Number of transactions started */ + private int transactionsStarted = 0; + + /** + * Transaction definition used by this test class: by default, a plain + * DefaultTransactionDefinition. Subclasses can change this to cause + * different behavior. + */ + protected TransactionDefinition transactionDefinition= new DefaultTransactionDefinition(); + + /** + * TransactionStatus for this test. Typical subclasses won't need to use it. + */ + protected TransactionStatus transactionStatus; + + + /** + * Default constructor for AbstractTransactionalSpringContextTests. + */ + public AbstractTransactionalSpringContextTests() { + } + + /** + * Constructor for AbstractTransactionalSpringContextTests with a JUnit name. + */ + public AbstractTransactionalSpringContextTests(String name) { + super(name); + } + + + /** + * Specify the transaction manager to use. No transaction management will be + * available if this is not set. Populated through dependency injection by + * the superclass. + *
+ * This mode works only if dependency checking is turned off in the + * {@link AbstractDependencyInjectionSpringContextTests} superclass. + */ + public void setTransactionManager(PlatformTransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Subclasses can set this value in their constructor to change the default, + * which is always to roll the transaction back. + */ + public void setDefaultRollback(final boolean defaultRollback) { + this.defaultRollback = defaultRollback; + } + /** + * Get the default rollback flag for this test. + * @see #setDefaultRollback(boolean) + * @return The default rollback flag. + */ + protected boolean isDefaultRollback() { + return this.defaultRollback; + } + + /** + * Determines whether or not to rollback transactions for the current test. + *
The default implementation delegates to {@link #isDefaultRollback()}. + * Subclasses can override as necessary. + */ + protected boolean isRollback() { + return isDefaultRollback(); + } + + /** + * Call this method in an overridden {@link #runBare()} method to prevent + * transactional execution. + */ + protected void preventTransaction() { + this.transactionDefinition = null; + } + + /** + * Call this method in an overridden {@link #runBare()} method to override + * the transaction attributes that will be used, so that {@link #setUp()} + * and {@link #tearDown()} behavior is modified. + * @param customDefinition the custom transaction definition + */ + protected void setTransactionDefinition(TransactionDefinition customDefinition) { + this.transactionDefinition = customDefinition; + } + + /** + * This implementation creates a transaction before test execution. + *
Override {@link #onSetUpBeforeTransaction()} and/or
+ * {@link #onSetUpInTransaction()} to add custom set-up behavior for
+ * transactional execution. Alternatively, override this method for general
+ * set-up behavior, calling super.onSetUp() as part of your
+ * method implementation.
+ * @throws Exception simply let any exception propagate
+ * @see #onTearDown()
+ */
+ protected void onSetUp() throws Exception {
+ this.complete = !this.isRollback();
+
+ if (this.transactionManager == null) {
+ this.logger.info("No transaction manager set: test will NOT run within a transaction");
+ }
+ else if (this.transactionDefinition == null) {
+ this.logger.info("No transaction definition set: test will NOT run within a transaction");
+ }
+ else {
+ onSetUpBeforeTransaction();
+ startNewTransaction();
+ try {
+ onSetUpInTransaction();
+ }
+ catch (final Exception ex) {
+ endTransaction();
+ throw ex;
+ }
+ }
+ }
+
+ /**
+ * Subclasses can override this method to perform any setup operations, such
+ * as populating a database table, before the transaction created by
+ * this class. Only invoked if there is a transaction: that is, if
+ * {@link #preventTransaction()} has not been invoked in an overridden
+ * {@link #runTest()} method.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onSetUpBeforeTransaction() throws Exception {
+ }
+
+ /**
+ * Subclasses can override this method to perform any setup operations, such
+ * as populating a database table, within the transaction created by
+ * this class.
+ *
NB: Not called if there is no transaction management, due to no + * transaction manager being provided in the context. + *
If any {@link Throwable} is thrown, the transaction that has been started + * prior to the execution of this method will be + * {@link #endTransaction() ended} (or rather an attempt will be made to + * {@link #endTransaction() end it gracefully}); The offending + * {@link Throwable} will then be rethrown. + * @throws Exception simply let any exception propagate + */ + protected void onSetUpInTransaction() throws Exception { + } + + /** + * This implementation ends the transaction after test execution. + *
Override {@link #onTearDownInTransaction()} and/or
+ * {@link #onTearDownAfterTransaction()} to add custom tear-down behavior
+ * for transactional execution. Alternatively, override this method for
+ * general tear-down behavior, calling super.onTearDown() as
+ * part of your method implementation.
+ *
Note that {@link #onTearDownInTransaction()} will only be called if a + * transaction is still active at the time of the test shutdown. In + * particular, it will not be called if the transaction has been + * completed with an explicit {@link #endTransaction()} call before. + * @throws Exception simply let any exception propagate + * @see #onSetUp() + */ + protected void onTearDown() throws Exception { + // Call onTearDownInTransaction and end transaction if the transaction + // is still active. + if (this.transactionStatus != null && !this.transactionStatus.isCompleted()) { + try { + onTearDownInTransaction(); + } + finally { + endTransaction(); + } + } + + // Call onTearDownAfterTransaction if there was at least one + // transaction, even if it has been completed early through an + // endTransaction() call. + if (this.transactionsStarted > 0) { + onTearDownAfterTransaction(); + } + } + + /** + * Subclasses can override this method to run invariant tests here. The + * transaction is still active at this point, so any changes made in + * the transaction will still be visible. However, there is no need to clean + * up the database, as a rollback will follow automatically. + *
NB: Not called if there is no actual transaction, for example due
+ * to no transaction manager being provided in the application context.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onTearDownInTransaction() throws Exception {
+ }
+
+ /**
+ * Subclasses can override this method to perform cleanup after a
+ * transaction here. At this point, the transaction is not active anymore.
+ * @throws Exception simply let any exception propagate
+ */
+ protected void onTearDownAfterTransaction() throws Exception {
+ }
+
+ /**
+ * Cause the transaction to commit for this test method, even if the test
+ * method is configured to {@link #isRollback() rollback}.
+ * @throws IllegalStateException if the operation cannot be set to complete
+ * as no transaction manager was provided
+ */
+ protected void setComplete() {
+ if (this.transactionManager == null) {
+ throw new IllegalStateException("No transaction manager set");
+ }
+ this.complete = true;
+ }
+
+ /**
+ * Immediately force a commit or rollback of the transaction, according to
+ * the complete and {@link #isRollback() rollback} flags.
+ *
Can be used to explicitly let the transaction end early, for example to + * check whether lazy associations of persistent objects work outside of a + * transaction (that is, have been initialized properly). + * @see #setComplete() + */ + protected void endTransaction() { + final boolean commit = this.complete || !isRollback(); + if (this.transactionStatus != null) { + try { + if (commit) { + this.transactionManager.commit(this.transactionStatus); + this.logger.debug("Committed transaction after execution of test [" + getName() + "]."); + } + else { + this.transactionManager.rollback(this.transactionStatus); + this.logger.debug("Rolled back transaction after execution of test [" + getName() + "]."); + } + } + finally { + this.transactionStatus = null; + } + } + } + + /** + * Start a new transaction. Only call this method if + * {@link #endTransaction()} has been called. {@link #setComplete()} can be + * used again in the new transaction. The fate of the new transaction, by + * default, will be the usual rollback. + * @throws TransactionException if starting the transaction failed + */ + protected void startNewTransaction() throws TransactionException { + if (this.transactionStatus != null) { + throw new IllegalStateException("Cannot start new transaction without ending existing transaction: " + + "Invoke endTransaction() before startNewTransaction()"); + } + if (this.transactionManager == null) { + throw new IllegalStateException("No transaction manager set"); + } + + this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition); + ++this.transactionsStarted; + this.complete = !this.isRollback(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Began transaction (" + this.transactionsStarted + "): transaction manager [" + + this.transactionManager + "]; rollback [" + this.isRollback() + "]."); + } + } + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/ConditionalTestCase.java b/org.springframework.orm/src/test/java/org/springframework/test/ConditionalTestCase.java new file mode 100644 index 00000000000..e44c9ac7963 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/ConditionalTestCase.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2007 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; + +import junit.framework.TestCase; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Superclass for JUnit 3.8 based tests that allows conditional test execution + * at the individual test method level. The + * {@link #isDisabledInThisEnvironment(String) isDisabledInThisEnvironment()} + * method is invoked before the execution of each test method. Subclasses can + * override that method to return whether or not the given test should be + * executed. Note that the tests will still appear to have executed and passed; + * however, log output will show that the test was not executed. + * + * @author Rod Johnson + * @since 2.0 + * @see #isDisabledInThisEnvironment + * @deprecated as of Spring 3.0, in favor of using the listener-based test context framework + * ({@link org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests}) + */ +@Deprecated +public abstract class ConditionalTestCase extends TestCase { + + private static int disabledTestCount; + + + /** + * Return the number of tests disabled in this environment. + */ + public static int getDisabledTestCount() { + return disabledTestCount; + } + + + /** Logger available to subclasses */ + protected final Log logger = LogFactory.getLog(getClass()); + + + /** + * Default constructor for ConditionalTestCase. + */ + public ConditionalTestCase() { + } + + /** + * Constructor for ConditionalTestCase with a JUnit name. + */ + public ConditionalTestCase(String name) { + super(name); + } + + public void runBare() throws Throwable { + // getName will return the name of the method being run + if (isDisabledInThisEnvironment(getName())) { + recordDisabled(); + this.logger.info("**** " + getClass().getName() + "." + getName() + " disabled in this environment: " + + "Total disabled tests = " + getDisabledTestCount()); + return; + } + + // Let JUnit handle execution + super.runBare(); + } + + /** + * Should this test run? + * @param testMethodName name of the test method + * @return whether the test should execute in the current environment + */ + protected boolean isDisabledInThisEnvironment(String testMethodName) { + return false; + } + + /** + * Record a disabled test. + * @return the current disabled test count + */ + protected int recordDisabled() { + return ++disabledTestCount; + } + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java b/org.springframework.orm/src/test/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java new file mode 100644 index 00000000000..c7c0e3df790 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/annotation/AbstractAnnotationAwareTransactionalTests.java @@ -0,0 +1,309 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Map; + +import javax.sql.DataSource; + +import junit.framework.AssertionFailedError; + +import org.springframework.context.ApplicationContext; +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; +import org.springframework.test.AbstractTransactionalDataSourceSpringContextTests; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionAttributeSource; +import org.springframework.util.Assert; + +/** + *
+ * Java 5 specific subclass of + * {@link AbstractTransactionalDataSourceSpringContextTests}, exposing a + * {@link SimpleJdbcTemplate} and obeying annotations for transaction control. + *
+ *+ * For example, test methods can be annotated with the regular Spring + * {@link org.springframework.transaction.annotation.Transactional @Transactional} + * annotation (e.g., to force execution in a read-only transaction) or with the + * {@link NotTransactional @NotTransactional} annotation to prevent any + * transaction being created at all. In addition, individual test methods can be + * annotated with {@link Rollback @Rollback} to override the + * {@link #isDefaultRollback() default rollback} settings. + *
+ *+ * The following list constitutes all annotations currently supported by + * AbstractAnnotationAwareTransactionalTests: + *
+ *Set to {@link SystemProfileValueSource} by default for backwards
+ * compatibility; however, the value may be changed in the
+ * {@link #AbstractAnnotationAwareTransactionalTests(String)} constructor.
+ */
+ protected ProfileValueSource profileValueSource = SystemProfileValueSource.getInstance();
+
+
+ /**
+ * Default constructor for AbstractAnnotationAwareTransactionalTests, which
+ * delegates to {@link #AbstractAnnotationAwareTransactionalTests(String)}.
+ */
+ public AbstractAnnotationAwareTransactionalTests() {
+ this(null);
+ }
+
+ /**
+ * Constructs a new AbstractAnnotationAwareTransactionalTests instance with
+ * the specified JUnit name and retrieves the configured (or
+ * default) {@link ProfileValueSource}.
+ * @param name the name of the current test
+ * @see ProfileValueUtils#retrieveProfileValueSource(Class)
+ */
+ public AbstractAnnotationAwareTransactionalTests(String name) {
+ super(name);
+ this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
+ }
+
+
+ @Override
+ public void setDataSource(DataSource dataSource) {
+ super.setDataSource(dataSource);
+ // JdbcTemplate will be identically configured
+ this.simpleJdbcTemplate = new SimpleJdbcTemplate(this.jdbcTemplate);
+ }
+
+ /**
+ * Search for a unique {@link ProfileValueSource} in the supplied
+ * {@link ApplicationContext}. If found, the
+ * profileValueSource for this test will be set to the unique
+ * {@link ProfileValueSource}.
+ * @param applicationContext the ApplicationContext in which to search for
+ * the ProfileValueSource; may not be null
+ * @deprecated Use {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration} instead.
+ */
+ @Deprecated
+ protected void findUniqueProfileValueSourceFromContext(ApplicationContext applicationContext) {
+ Assert.notNull(applicationContext, "Can not search for a ProfileValueSource in a null ApplicationContext.");
+ ProfileValueSource uniqueProfileValueSource = null;
+ Map, ?> beans = applicationContext.getBeansOfType(ProfileValueSource.class);
+ if (beans.size() == 1) {
+ uniqueProfileValueSource = (ProfileValueSource) beans.values().iterator().next();
+ }
+ if (uniqueProfileValueSource != null) {
+ this.profileValueSource = uniqueProfileValueSource;
+ }
+ }
+
+ /**
+ * Overridden to populate transaction definition from annotations.
+ */
+ @Override
+ public void runBare() throws Throwable {
+ // getName will return the name of the method being run.
+ if (isDisabledInThisEnvironment(getName())) {
+ // Let superclass log that we didn't run the test.
+ super.runBare();
+ return;
+ }
+
+ final Method testMethod = getTestMethod();
+
+ if (isDisabledInThisEnvironment(testMethod)) {
+ recordDisabled();
+ this.logger.info("**** " + getClass().getName() + "." + getName() + " disabled in this environment: "
+ + "Total disabled tests=" + getDisabledTestCount());
+ return;
+ }
+
+ TransactionDefinition explicitTransactionDefinition =
+ this.transactionAttributeSource.getTransactionAttribute(testMethod, getClass());
+ if (explicitTransactionDefinition != null) {
+ this.logger.info("Custom transaction definition [" + explicitTransactionDefinition + "] for test method ["
+ + getName() + "].");
+ setTransactionDefinition(explicitTransactionDefinition);
+ }
+ else if (testMethod.isAnnotationPresent(NotTransactional.class)) {
+ // Don't have any transaction...
+ preventTransaction();
+ }
+
+ // Let JUnit handle execution. We're just changing the state of the test class first.
+ runTestTimed(new TestExecutionCallback() {
+ public void run() throws Throwable {
+ try {
+ AbstractAnnotationAwareTransactionalTests.super.runBare();
+ }
+ finally {
+ // Mark the context to be blown away if the test was
+ // annotated to result in setDirty being invoked
+ // automatically.
+ if (testMethod.isAnnotationPresent(DirtiesContext.class)) {
+ AbstractAnnotationAwareTransactionalTests.this.setDirty();
+ }
+ }
+ }
+ }, testMethod);
+ }
+
+ /**
+ * Determine if the test for the supplied testMethod should
+ * run in the current environment.
+ *
The default implementation is based on
+ * {@link IfProfileValue @IfProfileValue} semantics.
+ * @param testMethod the test method
+ * @return true if the test is disabled in the current environment
+ * @see ProfileValueUtils#isTestEnabledInThisEnvironment
+ */
+ protected boolean isDisabledInThisEnvironment(Method testMethod) {
+ return !ProfileValueUtils.isTestEnabledInThisEnvironment(this.profileValueSource, testMethod, getClass());
+ }
+
+ /**
+ * Get the current test method.
+ */
+ protected Method getTestMethod() {
+ assertNotNull("TestCase.getName() cannot be null", getName());
+ Method testMethod = null;
+ try {
+ // Use same algorithm as JUnit itself to retrieve the test method
+ // about to be executed (the method name is returned by getName). It
+ // has to be public so we can retrieve it.
+ testMethod = getClass().getMethod(getName(), (Class[]) null);
+ }
+ catch (NoSuchMethodException ex) {
+ fail("Method '" + getName() + "' not found");
+ }
+ if (!Modifier.isPublic(testMethod.getModifiers())) {
+ fail("Method '" + getName() + "' should be public");
+ }
+ return testMethod;
+ }
+
+ /**
+ * Determine whether or not to rollback transactions for the current test
+ * by taking into consideration the
+ * {@link #isDefaultRollback() default rollback} flag and a possible
+ * method-level override via the {@link Rollback @Rollback} annotation.
+ * @return the rollback flag for the current test
+ */
+ @Override
+ protected boolean isRollback() {
+ boolean rollback = isDefaultRollback();
+ Rollback rollbackAnnotation = getTestMethod().getAnnotation(Rollback.class);
+ if (rollbackAnnotation != null) {
+ boolean rollbackOverride = rollbackAnnotation.value();
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("Method-level @Rollback(" + rollbackOverride + ") overrides default rollback ["
+ + rollback + "] for test [" + getName() + "].");
+ }
+ rollback = rollbackOverride;
+ }
+ else {
+ if (this.logger.isDebugEnabled()) {
+ this.logger.debug("No method-level @Rollback override: using default rollback [" + rollback
+ + "] for test [" + getName() + "].");
+ }
+ }
+ return rollback;
+ }
+
+ private void runTestTimed(TestExecutionCallback tec, Method testMethod) throws Throwable {
+ Timed timed = testMethod.getAnnotation(Timed.class);
+ if (timed == null) {
+ runTest(tec, testMethod);
+ }
+ else {
+ long startTime = System.currentTimeMillis();
+ try {
+ runTest(tec, testMethod);
+ }
+ finally {
+ long elapsed = System.currentTimeMillis() - startTime;
+ if (elapsed > timed.millis()) {
+ fail("Took " + elapsed + " ms; limit was " + timed.millis());
+ }
+ }
+ }
+ }
+
+ private void runTest(TestExecutionCallback tec, Method testMethod) throws Throwable {
+ ExpectedException expectedExceptionAnnotation = testMethod.getAnnotation(ExpectedException.class);
+ boolean exceptionIsExpected = (expectedExceptionAnnotation != null && expectedExceptionAnnotation.value() != null);
+ Class extends Throwable> expectedException = (exceptionIsExpected ? expectedExceptionAnnotation.value() : null);
+
+ Repeat repeat = testMethod.getAnnotation(Repeat.class);
+ int runs = ((repeat != null) && (repeat.value() > 1)) ? repeat.value() : 1;
+
+ for (int i = 0; i < runs; i++) {
+ try {
+ if (runs > 1 && this.logger != null && this.logger.isInfoEnabled()) {
+ this.logger.info("Repetition " + (i + 1) + " of test " + testMethod.getName());
+ }
+ tec.run();
+ if (exceptionIsExpected) {
+ fail("Expected exception: " + expectedException.getName());
+ }
+ }
+ catch (Throwable t) {
+ if (!exceptionIsExpected) {
+ throw t;
+ }
+ if (!expectedException.isAssignableFrom(t.getClass())) {
+ // Wrap the unexpected throwable with an explicit message.
+ AssertionFailedError assertionError = new AssertionFailedError("Unexpected exception, expected<" +
+ expectedException.getName() + "> but was<" + t.getClass().getName() + ">");
+ assertionError.initCause(t);
+ throw assertionError;
+ }
+ }
+ }
+ }
+
+
+ private static interface TestExecutionCallback {
+
+ void run() throws Throwable;
+ }
+
+}
diff --git a/org.springframework.orm/src/test/java/org/springframework/test/annotation/DirtiesContext.java b/org.springframework.orm/src/test/java/org/springframework/test/annotation/DirtiesContext.java
new file mode 100644
index 00000000000..288c5b0d42f
--- /dev/null
+++ b/org.springframework.orm/src/test/java/org/springframework/test/annotation/DirtiesContext.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2007 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * Test annotation to indicate that a test method dirties the context + * for the current test. + *
+ *
+ * Using this annotation in conjunction with
+ * {@link AbstractAnnotationAwareTransactionalTests} is less error-prone than
+ * calling
+ * {@link org.springframework.test.AbstractSingleSpringContextTests#setDirty() setDirty()}
+ * explicitly because the call to setDirty() is guaranteed to
+ * occur, even if the test failed. If only a particular code path in the test
+ * dirties the context, prefer calling setDirty() explicitly --
+ * and take care!
+ *
+ * Test annotation to indicate that a test is enabled for a specific testing + * profile or environment. If the configured {@link ProfileValueSource} returns + * a matching {@link #value() value} for the provided {@link #name() name}, the + * test will be enabled. + *
+ *+ * Note: {@link IfProfileValue @IfProfileValue} can be applied at either the + * class or method level. + *
+ *+ * Examples: when using {@link SystemProfileValueSource} as the + * {@link ProfileValueSource} implementation, you can configure a test method to + * run only on Java VMs from Sun Microsystems as follows: + *
+ * + *
+ * {@link IfProfileValue @IfProfileValue}(name="java.vendor", value="Sun Microsystems Inc.")
+ * testSomething() {
+ * // ...
+ * }
+ *
+ *
+ * + * You can alternatively configure {@link IfProfileValue @IfProfileValue} with + * OR semantics for multiple {@link #values() values} as follows + * (assuming a {@link ProfileValueSource} has been appropriately configured for + * the "test-groups" name): + *
+ * + *
+ * {@link IfProfileValue @IfProfileValue}(name="test-groups", values={"unit-tests", "integration-tests"})
+ * public void testWhichRunsForUnitOrIntegrationTestGroups() {
+ * // ...
+ * }
+ *
+ *
+ * @author Rod Johnson
+ * @author Sam Brannen
+ * @since 2.0
+ * @see ProfileValueSource
+ * @see ProfileValueSourceConfiguration
+ * @see ProfileValueUtils
+ * @see AbstractAnnotationAwareTransactionalTests
+ * @see org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests
+ * @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
+ * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+@Documented
+public @interface IfProfileValue {
+
+ /**
+ * The name of the profile value against which to test.
+ */
+ String name();
+
+ /**
+ * A single, permissible value of the profile value
+ * for the given {@link #name() name}.
+ * Note: Assigning values to both {@link #value()} and {@link #values()}
+ * will lead to a configuration conflict.
+ */
+ String value() default "";
+
+ /**
+ * A list of all permissible values of the
+ * profile value for the given {@link #name() name}.
+ *
Note: Assigning values to both {@link #value()} and {@link #values()} + * will lead to a configuration conflict. + */ + String[] values() default {}; + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/annotation/NotTransactional.java b/org.springframework.orm/src/test/java/org/springframework/test/annotation/NotTransactional.java new file mode 100644 index 00000000000..9b3df718069 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/annotation/NotTransactional.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2007 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.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Test annotation to indicate that a method is not transactional. + * + * @author Rod Johnson + * @author Sam Brannen + * @since 2.0 + */ +@Target( { ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NotTransactional { + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueSource.java b/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueSource.java new file mode 100644 index 00000000000..186e90321e9 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueSource.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2008 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.annotation; + +/** + *
+ * Strategy interface for retrieving profile values for a given + * testing environment. + *
+ *
+ * Concrete implementations must provide a public no-args
+ * constructor.
+ *
+ * Spring provides the following out-of-the-box implementations: + *
+ *null
+ * if there is no profile value with that key
+ */
+ String get(String key);
+
+}
diff --git a/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java b/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java
new file mode 100644
index 00000000000..dafbe4f1a16
--- /dev/null
+++ b/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueSourceConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2007 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * + * ProfileValueSourceConfiguration is a class-level annotation which is used to + * specify what type of {@link ProfileValueSource} to use when retrieving + * profile values configured via the + * {@link IfProfileValue @IfProfileValue} annotation. + *
+ * + * @author Sam Brannen + * @since 2.5 + * @see ProfileValueSource + * @see IfProfileValue + * @see ProfileValueUtils + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@Documented +public @interface ProfileValueSourceConfiguration { + + /** + *+ * The type of {@link ProfileValueSource} to use when retrieving + * profile values. + *
+ * + * @see SystemProfileValueSource + */ + Class extends ProfileValueSource> value() default SystemProfileValueSource.class; + +} diff --git a/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueUtils.java b/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueUtils.java new file mode 100644 index 00000000000..ea1a6797af5 --- /dev/null +++ b/org.springframework.orm/src/test/java/org/springframework/test/annotation/ProfileValueUtils.java @@ -0,0 +1,213 @@ +/* + * Copyright 2002-2008 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.annotation; + +import java.lang.reflect.Method; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * General utility methods for working with profile values. + * + * @author Sam Brannen + * @author Juergen Hoeller + * @since 2.5 + * @see ProfileValueSource + * @see ProfileValueSourceConfiguration + * @see IfProfileValue + */ +public abstract class ProfileValueUtils { + + private static final Log logger = LogFactory.getLog(ProfileValueUtils.class); + + + /** + * Retrieves the {@link ProfileValueSource} type for the specified + * {@link Class test class} as configured via the + * {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration} + * annotation and instantiates a new instance of that type. + *
+ * If
+ * {@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}
+ * is not present on the specified class or if a custom
+ * {@link ProfileValueSource} is not declared, the default
+ * {@link SystemProfileValueSource} will be returned instead.
+ *
+ * @param testClass The test class for which the ProfileValueSource should
+ * be retrieved
+ * @return the configured (or default) ProfileValueSource for the specified
+ * class
+ * @see SystemProfileValueSource
+ */
+ @SuppressWarnings("unchecked")
+ public static ProfileValueSource retrieveProfileValueSource(Class> testClass) {
+ Assert.notNull(testClass, "testClass must not be null");
+
+ Class
+ * Defaults to
+ * Defaults to
+ * Defaults to
+ * Whether or not the transaction for the annotated method should be rolled
+ * back after the method has completed.
+ *
+ * Test-specific annotation to indicate that a test method has to finish
+ * execution in a {@link #millis() specified time period}.
+ *
+ * If the text execution takes longer than the specified time period, then the
+ * test is to be considered failed.
+ *
+ * Note that the time period includes execution of the test method itself, any
+ * {@link Repeat repetitions} of the test, and any set up or
+ * tear down of the test fixture.
+ * 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:
+ * The default implementation deactivates shadow class loading if Spring's
+ * InstrumentationSavingAgent has been configured on VM startup.
+ */
+ protected boolean shouldUseShadowLoader() {
+ return !InstrumentationLoadTimeWeaver.isInstrumentationAvailable();
+ }
+
+ @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;
+
+ public ShadowingLoadTimeWeaver(ClassLoader shadowingClassLoader) {
+ this.shadowingClassLoader = shadowingClassLoader;
+ }
+
+ public void addTransformer(ClassFileTransformer transformer) {
+ try {
+ Method addClassFileTransformer =
+ this.shadowingClassLoader.getClass().getMethod("addTransformer", ClassFileTransformer.class);
+ addClassFileTransformer.setAccessible(true);
+ addClassFileTransformer.invoke(this.shadowingClassLoader, transformer);
+ }
+ catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public ClassLoader getInstrumentableClassLoader() {
+ return 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 (this.shadowingClassLoader instanceof ShadowingClassLoader) {
+ roscl.copyTransformers((ShadowingClassLoader) this.shadowingClassLoader);
+ }
+ if (this.shadowingClassLoader instanceof ResourceOverridingShadowingClassLoader) {
+ roscl.copyOverrides((ResourceOverridingShadowingClassLoader) this.shadowingClassLoader);
+ }
+ return roscl;
+ }
+ }
+
+}
diff --git a/org.springframework.orm/src/test/java/org/springframework/test/jpa/OrmXmlOverridingShadowingClassLoader.java b/org.springframework.orm/src/test/java/org/springframework/test/jpa/OrmXmlOverridingShadowingClassLoader.java
new file mode 100644
index 00000000000..a6baeaffcd2
--- /dev/null
+++ b/org.springframework.orm/src/test/java/org/springframework/test/jpa/OrmXmlOverridingShadowingClassLoader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2007 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 org.springframework.instrument.classloading.ResourceOverridingShadowingClassLoader;
+
+/**
+ * Subclass of ShadowingClassLoader that overrides attempts to
+ * locate This class must not be an inner class of AbstractJpaTests
+ * to avoid it being loaded until first used.
+ *
+ * @author Rod Johnson
+ * @author Adrian Colyer
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+class OrmXmlOverridingShadowingClassLoader extends ResourceOverridingShadowingClassLoader {
+
+ /**
+ * Default location of the testClass is enabled
+ * in the current environment, as specified by the
+ * {@link IfProfileValue @IfProfileValue} annotation at the class level.
+ * true if no
+ * {@link IfProfileValue @IfProfileValue} annotation is declared.
+ *
+ * @param testClass the test class
+ * @return true if the test is enabled in the
+ * current environment
+ */
+ public static boolean isTestEnabledInThisEnvironment(Class> testClass) {
+ IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
+ if (ifProfileValue == null) {
+ return true;
+ }
+ ProfileValueSource profileValueSource = retrieveProfileValueSource(testClass);
+ return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
+ }
+
+ /**
+ * Determine if the supplied testMethod is enabled
+ * in the current environment, as specified by the
+ * {@link IfProfileValue @IfProfileValue} annotation, which may be declared
+ * on the test method itself or at the class level.
+ * true if no
+ * {@link IfProfileValue @IfProfileValue} annotation is declared.
+ *
+ * @param testMethod the test method
+ * @param testClass the test class
+ * @return true if the test is enabled in the
+ * current environment
+ */
+ public static boolean isTestEnabledInThisEnvironment(Method testMethod, Class> testClass) {
+ IfProfileValue ifProfileValue = testMethod.getAnnotation(IfProfileValue.class);
+ if (ifProfileValue == null) {
+ ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
+ if (ifProfileValue == null) {
+ return true;
+ }
+ }
+ ProfileValueSource profileValueSource = retrieveProfileValueSource(testClass);
+ return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
+ }
+
+ /**
+ * Determine if the supplied testMethod is enabled
+ * in the current environment, as specified by the
+ * {@link IfProfileValue @IfProfileValue} annotation, which may be declared
+ * on the test method itself or at the class level.
+ * true if no
+ * {@link IfProfileValue @IfProfileValue} annotation is declared.
+ *
+ * @param profileValueSource the ProfileValueSource to use to determine if
+ * the test is enabled
+ * @param testMethod the test method
+ * @param testClass the test class
+ * @return true if the test is enabled in the
+ * current environment
+ */
+ public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod,
+ Class> testClass) {
+
+ IfProfileValue ifProfileValue = testMethod.getAnnotation(IfProfileValue.class);
+ if (ifProfileValue == null) {
+ ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
+ if (ifProfileValue == null) {
+ return true;
+ }
+ }
+ return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
+ }
+
+ /**
+ * Determine if the value (or one of the values)
+ * in the supplied {@link IfProfileValue @IfProfileValue} annotation is
+ * enabled in the current environment.
+ *
+ * @param profileValueSource the ProfileValueSource to use to determine if
+ * the test is enabled
+ * @param ifProfileValue the annotation to introspect
+ * @return true if the test is enabled in the
+ * current environment
+ */
+ private static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource,
+ IfProfileValue ifProfileValue) {
+
+ String environmentValue = profileValueSource.get(ifProfileValue.name());
+ String[] annotatedValues = ifProfileValue.values();
+ if (StringUtils.hasLength(ifProfileValue.value())) {
+ if (annotatedValues.length > 0) {
+ throw new IllegalArgumentException("Setting both the 'value' and 'values' attributes "
+ + "of @IfProfileValue is not allowed: choose one or the other.");
+ }
+ annotatedValues = new String[] { ifProfileValue.value() };
+ }
+
+ for (String value : annotatedValues) {
+ if (ObjectUtils.nullSafeEquals(value, environmentValue)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/org.springframework.orm/src/test/java/org/springframework/test/annotation/Repeat.java b/org.springframework.orm/src/test/java/org/springframework/test/annotation/Repeat.java
new file mode 100644
index 00000000000..b2e832fac3f
--- /dev/null
+++ b/org.springframework.orm/src/test/java/org/springframework/test/annotation/Repeat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2008 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.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Test annotation to indicate that a test method should be invoked repeatedly.
+ * true, the transaction will be rolled back;
+ * otherwise, the transaction will be committed.
+ *
+ * @author Sam Brannen
+ * @since 2.5
+ */
+@Target( { ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Rollback {
+
+ /**
+ * LineNumberReader> containing the script to be processed
+ * @return String containing the script lines
+ * @throws IOException
+ */
+ public static String readScript(LineNumberReader lineNumberReader) throws IOException {
+ String currentStatement = lineNumberReader.readLine();
+ StringBuilder scriptBuilder = new StringBuilder();
+ while (currentStatement != null) {
+ if (StringUtils.hasText(currentStatement)) {
+ if (scriptBuilder.length() > 0) {
+ scriptBuilder.append('\n');
+ }
+ scriptBuilder.append(currentStatement);
+ }
+ currentStatement = lineNumberReader.readLine();
+ }
+ return scriptBuilder.toString();
+ }
+
+ /**
+ * Does the provided SQL script contain the specified delimiter?
+ * @param script the SQL script
+ * @param delim charecter delimiting each statement - typically a ';' character
+ */
+ public static boolean containsSqlScriptDelimiters(String script, char delim) {
+ boolean inLiteral = false;
+ char[] content = script.toCharArray();
+ for (int i = 0; i < script.length(); i++) {
+ if (content[i] == '\'') {
+ inLiteral = !inLiteral;
+ }
+ if (content[i] == delim && !inLiteral) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Split an SQL script into separate statements delimited with the provided delimiter character. Each
+ * individual statement will be added to the provided List.
+ * @param script the SQL script
+ * @param delim charecter delimiting each statement - typically a ';' character
+ * @param statements the List that will contain the individual statements
+ */
+ public static void splitSqlScript(String script, char delim, List
+ * <dependency>
+ * <groupId>xerces</groupId>
+ * <artifactId>xercesImpl</artifactId>
+ * <version>2.8.1</version>
+ * </dependency>
+ *
+ *
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Juergen Hoeller
+ * @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 MapEntityManagerFactory.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.
+ * orm.xml.
+ *
+ * orm.xml file in the class path:
+ * "META-INF/orm.xml"
+ */
+ public static final String DEFAULT_ORM_XML_LOCATION = "META-INF/orm.xml";
+
+
+ public OrmXmlOverridingShadowingClassLoader(ClassLoader loader, String realOrmXmlLocation) {
+ super(loader);
+
+ // Automatically exclude classes from these well-known persistence providers.
+ // Do NOT exclude Hibernate classes --
+ // this causes class casts due to use of CGLIB by Hibernate.
+ // Same goes for OpenJPA which will not enhance the domain classes.
+ excludePackage("oracle.toplink.essentials");
+ excludePackage("junit");
+
+ override(DEFAULT_ORM_XML_LOCATION, realOrmXmlLocation);
+ }
+
+}
diff --git a/org.springframework.orm/src/test/java/org/springframework/transaction/MockJtaTransaction.java b/org.springframework.orm/src/test/java/org/springframework/transaction/MockJtaTransaction.java
new file mode 100644
index 00000000000..c9c8709c48b
--- /dev/null
+++ b/org.springframework.orm/src/test/java/org/springframework/transaction/MockJtaTransaction.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2005 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.transaction;
+
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.xa.XAResource;
+
+/**
+ * @author Juergen Hoeller
+ * @since 31.08.2004
+ */
+public class MockJtaTransaction implements javax.transaction.Transaction {
+
+ private Synchronization synchronization;
+
+ public int getStatus() {
+ return Status.STATUS_ACTIVE;
+ }
+
+ public void registerSynchronization(Synchronization synchronization) {
+ this.synchronization = synchronization;
+ }
+
+ public Synchronization getSynchronization() {
+ return synchronization;
+ }
+
+ public boolean enlistResource(XAResource xaResource) {
+ return false;
+ }
+
+ public boolean delistResource(XAResource xaResource, int i) {
+ return false;
+ }
+
+ public void commit() {
+ }
+
+ public void rollback() {
+ }
+
+ public void setRollbackOnly() {
+ }
+
+}
diff --git a/org.springframework.orm/src/test/java/org/springframework/util/SerializationTestUtils.java b/org.springframework.orm/src/test/java/org/springframework/util/SerializationTestUtils.java
new file mode 100644
index 00000000000..dbe6421093e
--- /dev/null
+++ b/org.springframework.orm/src/test/java/org/springframework/util/SerializationTestUtils.java
@@ -0,0 +1,97 @@
+/*
+ * The Spring Framework is published under the terms
+ * of the Apache Software License.
+ */
+
+package org.springframework.util;
+
+import java.awt.Point;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+
+import junit.framework.TestCase;
+
+import org.springframework.beans.TestBean;
+
+/**
+ * Utilities for testing serializability of objects.
+ * Exposes static methods for use in other test cases.
+ * Extends TestCase only to test itself.
+ *
+ * @author Rod Johnson
+ */
+public class SerializationTestUtils extends TestCase {
+
+ public static void testSerialization(Object o) throws IOException {
+ OutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(o);
+ }
+
+ public static boolean isSerializable(Object o) throws IOException {
+ try {
+ testSerialization(o);
+ return true;
+ }
+ catch (NotSerializableException ex) {
+ return false;
+ }
+ }
+
+ public static Object serializeAndDeserialize(Object o) throws IOException, ClassNotFoundException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(o);
+ oos.flush();
+ baos.flush();
+ byte[] bytes = baos.toByteArray();
+
+ ByteArrayInputStream is = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(is);
+ Object o2 = ois.readObject();
+
+ return o2;
+ }
+
+ public SerializationTestUtils(String s) {
+ super(s);
+ }
+
+ public void testWithNonSerializableObject() throws IOException {
+ TestBean o = new TestBean();
+ assertFalse(o instanceof Serializable);
+
+ assertFalse(isSerializable(o));
+
+ try {
+ testSerialization(o);
+ fail();
+ }
+ catch (NotSerializableException ex) {
+ // Ok
+ }
+ }
+
+ public void testWithSerializableObject() throws Exception {
+ int x = 5;
+ int y = 10;
+ Point p = new Point(x, y);
+ assertTrue(p instanceof Serializable);
+
+ testSerialization(p);
+
+ assertTrue(isSerializable(p));
+
+ Point p2 = (Point) serializeAndDeserialize(p);
+ assertNotSame(p, p2);
+ assertEquals(x, (int) p2.getX());
+ assertEquals(y, (int) p2.getY());
+ }
+
+}
\ No newline at end of file
diff --git a/org.springframework.testsuite/src/test/resources/order-supplemental.jar b/org.springframework.orm/src/test/resources/order-supplemental.jar
similarity index 100%
rename from org.springframework.testsuite/src/test/resources/order-supplemental.jar
rename to org.springframework.orm/src/test/resources/order-supplemental.jar
diff --git a/org.springframework.testsuite/src/test/resources/order.jar b/org.springframework.orm/src/test/resources/order.jar
similarity index 100%
rename from org.springframework.testsuite/src/test/resources/order.jar
rename to org.springframework.orm/src/test/resources/order.jar
diff --git a/org.springframework.test/.classpath b/org.springframework.test/.classpath
index 48537d7ca2e..62b0d07af7e 100644
--- a/org.springframework.test/.classpath
+++ b/org.springframework.test/.classpath
@@ -13,7 +13,6 @@