Rename modules {org.springframework.*=>spring-*}

This renaming more intuitively expresses the relationship between
subprojects and the JAR artifacts they produce.

Tracking history across these renames is possible, but it requires
use of the --follow flag to `git log`, for example

    $ git log spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history up until the renaming event, where

    $ git log --follow spring-aop/src/main/java/org/springframework/aop/Advisor.java

will show history for all changes to the file, before and after the
renaming.

See http://chrisbeams.com/git-diff-across-renamed-directories
This commit is contained in:
Chris Beams
2012-01-20 22:51:02 +01:00
parent b6cb514d38
commit 02a4473c62
5671 changed files with 20 additions and 32 deletions

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2011 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.mock.env;
import java.util.Properties;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
/**
* Simple {@link PropertySource} implementation for use in testing. Accepts
* a user-provided {@link Properties} object, or if omitted during construction,
* the implementation will initialize its own.
*
* The {@link #setProperty} and {@link #withProperty} methods are exposed for
* convenience, for example:
* <pre class="code">
* {@code
* PropertySource<?> source = new MockPropertySource().withProperty("foo", "bar");
* }
* </pre>
*
* @author Chris Beams
* @since 3.1
* @see MockEnvironment
*/
public class MockPropertySource extends PropertiesPropertySource {
/**
* {@value} is the default name for {@link MockPropertySource} instances not
* otherwise given an explicit name.
* @see #MockPropertySource()
* @see #MockPropertySource(String)
*/
public static final String MOCK_PROPERTIES_PROPERTY_SOURCE_NAME = "mockProperties";
/**
* Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME}
* that will maintain its own internal {@link Properties} instance.
*/
public MockPropertySource() {
this(new Properties());
}
/**
* Create a new {@code MockPropertySource} with the given name that will
* maintain its own internal {@link Properties} instance.
* @param name the {@linkplain #getName() name} of the property source
*/
public MockPropertySource(String name) {
this(name, new Properties());
}
/**
* Create a new {@code MockPropertySource} named {@value #MOCK_PROPERTIES_PROPERTY_SOURCE_NAME}
* and backed by the given {@link Properties} object.
* @param properties the properties to use
*/
public MockPropertySource(Properties properties) {
this(MOCK_PROPERTIES_PROPERTY_SOURCE_NAME, properties);
}
/**
* Create a new {@code MockPropertySource} with with the given name and backed by the given
* {@link Properties} object
* @param name the {@linkplain #getName() name} of the property source
* @param properties the properties to use
*/
public MockPropertySource(String name, Properties properties) {
super(name, properties);
}
/**
* Set the given property on the underlying {@link Properties} object.
*/
public void setProperty(String name, Object value) {
this.source.put(name, value);
}
/**
* Convenient synonym for {@link #setProperty} that returns the current instance.
* Useful for method chaining and fluent-style use.
* @return this {@link MockPropertySource} instance
*/
public MockPropertySource withProperty(String name, Object value) {
this.setProperty(name, value);
return this;
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2002-2009 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.mock.jndi;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.naming.NamingException;
import org.springframework.jndi.JndiTemplate;
/**
* Simple extension of the JndiTemplate class that always returns a given
* object. Very useful for testing. Effectively a mock object.
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public class ExpectedLookupTemplate extends JndiTemplate {
private final Map<String, Object> jndiObjects = new ConcurrentHashMap<String, Object>();
/**
* Construct a new JndiTemplate that will always return given objects for
* given names. To be populated through <code>addObject</code> calls.
*
* @see #addObject(String, Object)
*/
public ExpectedLookupTemplate() {
}
/**
* Construct a new JndiTemplate that will always return the given object,
* but honour only requests for the given name.
*
* @param name the name the client is expected to look up
* @param object the object that will be returned
*/
public ExpectedLookupTemplate(String name, Object object) {
addObject(name, object);
}
/**
* Add the given object to the list of JNDI objects that this template will
* expose.
*
* @param name the name the client is expected to look up
* @param object the object that will be returned
*/
public void addObject(String name, Object object) {
this.jndiObjects.put(name, object);
}
/**
* If the name is the expected name specified in the constructor, return the
* object provided in the constructor. If the name is unexpected, a
* respective NamingException gets thrown.
*/
public Object lookup(String name) throws NamingException {
Object object = this.jndiObjects.get(name);
if (object == null) {
throw new NamingException("Unexpected JNDI name '" + name + "': expecting " + this.jndiObjects.keySet());
}
return object;
}
}

View File

@@ -0,0 +1,345 @@
/*
* 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.mock.jndi;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NameParser;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
/**
* Simple implementation of a JNDI naming context.
* Only supports binding plain Objects to String names.
* Mainly for test environments, but also usable for standalone applications.
*
* <p>This class is not intended for direct usage by applications, although it
* can be used for example to override JndiTemplate's <code>createInitialContext</code>
* method in unit tests. Typically, SimpleNamingContextBuilder will be used to
* set up a JVM-level JNDI environment.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see org.springframework.mock.jndi.SimpleNamingContextBuilder
* @see org.springframework.jndi.JndiTemplate#createInitialContext
*/
public class SimpleNamingContext implements Context {
private final Log logger = LogFactory.getLog(getClass());
private final String root;
private final Hashtable<String, Object> boundObjects;
private final Hashtable<String, Object> environment = new Hashtable<String, Object>();
/**
* Create a new naming context.
*/
public SimpleNamingContext() {
this("");
}
/**
* Create a new naming context with the given naming root.
*/
public SimpleNamingContext(String root) {
this.root = root;
this.boundObjects = new Hashtable<String, Object>();
}
/**
* Create a new naming context with the given naming root,
* the given name/object map, and the JNDI environment entries.
*/
public SimpleNamingContext(String root, Hashtable<String, Object> boundObjects, Hashtable<String, Object> env) {
this.root = root;
this.boundObjects = boundObjects;
if (env != null) {
this.environment.putAll(env);
}
}
// Actual implementations of Context methods follow
public NamingEnumeration<NameClassPair> list(String root) throws NamingException {
if (logger.isDebugEnabled()) {
logger.debug("Listing name/class pairs under [" + root + "]");
}
return new NameClassPairEnumeration(this, root);
}
public NamingEnumeration<Binding> listBindings(String root) throws NamingException {
if (logger.isDebugEnabled()) {
logger.debug("Listing bindings under [" + root + "]");
}
return new BindingEnumeration(this, root);
}
/**
* Look up the object with the given name.
* <p>Note: Not intended for direct use by applications.
* Will be used by any standard InitialContext JNDI lookups.
* @throws javax.naming.NameNotFoundException if the object could not be found
*/
public Object lookup(String lookupName) throws NameNotFoundException {
String name = this.root + lookupName;
if (logger.isDebugEnabled()) {
logger.debug("Static JNDI lookup: [" + name + "]");
}
if ("".equals(name)) {
return new SimpleNamingContext(this.root, this.boundObjects, this.environment);
}
Object found = this.boundObjects.get(name);
if (found == null) {
if (!name.endsWith("/")) {
name = name + "/";
}
for (String boundName : this.boundObjects.keySet()) {
if (boundName.startsWith(name)) {
return new SimpleNamingContext(name, this.boundObjects, this.environment);
}
}
throw new NameNotFoundException(
"Name [" + this.root + lookupName + "] not bound; " + this.boundObjects.size() + " bindings: [" +
StringUtils.collectionToDelimitedString(this.boundObjects.keySet(), ",") + "]");
}
return found;
}
public Object lookupLink(String name) throws NameNotFoundException {
return lookup(name);
}
/**
* Bind the given object to the given name.
* Note: Not intended for direct use by applications
* if setting up a JVM-level JNDI environment.
* Use SimpleNamingContextBuilder to set up JNDI bindings then.
* @see org.springframework.mock.jndi.SimpleNamingContextBuilder#bind
*/
public void bind(String name, Object obj) {
if (logger.isInfoEnabled()) {
logger.info("Static JNDI binding: [" + this.root + name + "] = [" + obj + "]");
}
this.boundObjects.put(this.root + name, obj);
}
public void unbind(String name) {
if (logger.isInfoEnabled()) {
logger.info("Static JNDI remove: [" + this.root + name + "]");
}
this.boundObjects.remove(this.root + name);
}
public void rebind(String name, Object obj) {
bind(name, obj);
}
public void rename(String oldName, String newName) throws NameNotFoundException {
Object obj = lookup(oldName);
unbind(oldName);
bind(newName, obj);
}
public Context createSubcontext(String name) {
String subcontextName = this.root + name;
if (!subcontextName.endsWith("/")) {
subcontextName += "/";
}
Context subcontext = new SimpleNamingContext(subcontextName, this.boundObjects, this.environment);
bind(name, subcontext);
return subcontext;
}
public void destroySubcontext(String name) {
unbind(name);
}
public String composeName(String name, String prefix) {
return prefix + name;
}
public Hashtable<String, Object> getEnvironment() {
return this.environment;
}
public Object addToEnvironment(String propName, Object propVal) {
return this.environment.put(propName, propVal);
}
public Object removeFromEnvironment(String propName) {
return this.environment.remove(propName);
}
public void close() {
}
// Unsupported methods follow: no support for javax.naming.Name
public NamingEnumeration<NameClassPair> list(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public NamingEnumeration<Binding> listBindings(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public Object lookup(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public Object lookupLink(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public void bind(Name name, Object obj) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public void unbind(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public void rebind(Name name, Object obj) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public void rename(Name oldName, Name newName) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public Context createSubcontext(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public void destroySubcontext(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public String getNameInNamespace() throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public NameParser getNameParser(Name name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public NameParser getNameParser(String name) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
public Name composeName(Name name, Name prefix) throws NamingException {
throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
}
private static abstract class AbstractNamingEnumeration<T> implements NamingEnumeration<T> {
private Iterator<T> iterator;
private AbstractNamingEnumeration(SimpleNamingContext context, String proot) throws NamingException {
if (!"".equals(proot) && !proot.endsWith("/")) {
proot = proot + "/";
}
String root = context.root + proot;
Map<String, T> contents = new HashMap<String, T>();
for (String boundName : context.boundObjects.keySet()) {
if (boundName.startsWith(root)) {
int startIndex = root.length();
int endIndex = boundName.indexOf('/', startIndex);
String strippedName =
(endIndex != -1 ? boundName.substring(startIndex, endIndex) : boundName.substring(startIndex));
if (!contents.containsKey(strippedName)) {
try {
contents.put(strippedName, createObject(strippedName, context.lookup(proot + strippedName)));
}
catch (NameNotFoundException ex) {
// cannot happen
}
}
}
}
if (contents.size() == 0) {
throw new NamingException("Invalid root: [" + context.root + proot + "]");
}
this.iterator = contents.values().iterator();
}
protected abstract T createObject(String strippedName, Object obj);
public boolean hasMore() {
return this.iterator.hasNext();
}
public T next() {
return this.iterator.next();
}
public boolean hasMoreElements() {
return this.iterator.hasNext();
}
public T nextElement() {
return this.iterator.next();
}
public void close() {
}
}
private static class NameClassPairEnumeration extends AbstractNamingEnumeration<NameClassPair> {
private NameClassPairEnumeration(SimpleNamingContext context, String root) throws NamingException {
super(context, root);
}
protected NameClassPair createObject(String strippedName, Object obj) {
return new NameClassPair(strippedName, obj.getClass().getName());
}
}
private static class BindingEnumeration extends AbstractNamingEnumeration<Binding> {
private BindingEnumeration(SimpleNamingContext context, String root) throws NamingException {
super(context, root);
}
protected Binding createObject(String strippedName, Object obj) {
return new Binding(strippedName, obj);
}
}
}

View File

@@ -0,0 +1,235 @@
/*
* Copyright 2002-2009 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.mock.jndi;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.InitialContextFactoryBuilder;
import javax.naming.spi.NamingManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;
/**
* Simple implementation of a JNDI naming context builder.
*
* <p>Mainly targeted at test environments, where each test case can
* configure JNDI appropriately, so that <code>new InitialContext()</code>
* will expose the required objects. Also usable for standalone applications,
* e.g. for binding a JDBC DataSource to a well-known JNDI location, to be
* able to use traditional J2EE data access code outside of a J2EE container.
*
* <p>There are various choices for DataSource implementations:
* <ul>
* <li>SingleConnectionDataSource (using the same Connection for all getConnection calls);
* <li>DriverManagerDataSource (creating a new Connection on each getConnection call);
* <li>Apache's Jakarta Commons DBCP offers BasicDataSource (a real pool).
* </ul>
*
* <p>Typical usage in bootstrap code:
*
* <pre class="code">
* SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
* DataSource ds = new DriverManagerDataSource(...);
* builder.bind("java:comp/env/jdbc/myds", ds);
* builder.activate();</pre>
*
* Note that it's impossible to activate multiple builders within the same JVM,
* due to JNDI restrictions. Thus to configure a fresh builder repeatedly, use
* the following code to get a reference to either an already activated builder
* or a newly activated one:
*
* <pre class="code">
* SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
* DataSource ds = new DriverManagerDataSource(...);
* builder.bind("java:comp/env/jdbc/myds", ds);</pre>
*
* Note that you <i>should not</i> call <code>activate()</code> on a builder from
* this factory method, as there will already be an activated one in any case.
*
* <p>An instance of this class is only necessary at setup time.
* An application does not need to keep a reference to it after activation.
*
* @author Juergen Hoeller
* @author Rod Johnson
* @see #emptyActivatedContextBuilder()
* @see #bind(String, Object)
* @see #activate()
* @see org.springframework.mock.jndi.SimpleNamingContext
* @see org.springframework.jdbc.datasource.SingleConnectionDataSource
* @see org.springframework.jdbc.datasource.DriverManagerDataSource
* @see org.apache.commons.dbcp.BasicDataSource
*/
public class SimpleNamingContextBuilder implements InitialContextFactoryBuilder {
/** An instance of this class bound to JNDI */
private static volatile SimpleNamingContextBuilder activated;
private static boolean initialized = false;
private static final Object initializationLock = new Object();
/**
* Checks if a SimpleNamingContextBuilder is active.
* @return the current SimpleNamingContextBuilder instance,
* or <code>null</code> if none
*/
public static SimpleNamingContextBuilder getCurrentContextBuilder() {
return activated;
}
/**
* If no SimpleNamingContextBuilder is already configuring JNDI,
* create and activate one. Otherwise take the existing activate
* SimpleNamingContextBuilder, clear it and return it.
* <p>This is mainly intended for test suites that want to
* reinitialize JNDI bindings from scratch repeatedly.
* @return an empty SimpleNamingContextBuilder that can be used
* to control JNDI bindings
*/
public static SimpleNamingContextBuilder emptyActivatedContextBuilder() throws NamingException {
if (activated != null) {
// Clear already activated context builder.
activated.clear();
}
else {
// Create and activate new context builder.
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
// The activate() call will cause an assignment to the activated variable.
builder.activate();
}
return activated;
}
private final Log logger = LogFactory.getLog(getClass());
private final Hashtable<String,Object> boundObjects = new Hashtable<String,Object>();
/**
* Register the context builder by registering it with the JNDI NamingManager.
* Note that once this has been done, <code>new InitialContext()</code> will always
* return a context from this factory. Use the <code>emptyActivatedContextBuilder()</code>
* static method to get an empty context (for example, in test methods).
* @throws IllegalStateException if there's already a naming context builder
* registered with the JNDI NamingManager
*/
public void activate() throws IllegalStateException, NamingException {
logger.info("Activating simple JNDI environment");
synchronized (initializationLock) {
if (!initialized) {
if (NamingManager.hasInitialContextFactoryBuilder()) {
throw new IllegalStateException(
"Cannot activate SimpleNamingContextBuilder: there is already a JNDI provider registered. " +
"Note that JNDI is a JVM-wide service, shared at the JVM system class loader level, " +
"with no reset option. As a consequence, a JNDI provider must only be registered once per JVM.");
}
NamingManager.setInitialContextFactoryBuilder(this);
initialized = true;
}
}
activated = this;
}
/**
* Temporarily deactivate this context builder. It will remain registered with
* the JNDI NamingManager but will delegate to the standard JNDI InitialContextFactory
* (if configured) instead of exposing its own bound objects.
* <p>Call <code>activate()</code> again in order to expose this context builder's own
* bound objects again. Such activate/deactivate sequences can be applied any number
* of times (e.g. within a larger integration test suite running in the same VM).
* @see #activate()
*/
public void deactivate() {
logger.info("Deactivating simple JNDI environment");
activated = null;
}
/**
* Clear all bindings in this context builder, while keeping it active.
*/
public void clear() {
this.boundObjects.clear();
}
/**
* Bind the given object under the given name, for all naming contexts
* that this context builder will generate.
* @param name the JNDI name of the object (e.g. "java:comp/env/jdbc/myds")
* @param obj the object to bind (e.g. a DataSource implementation)
*/
public void bind(String name, Object obj) {
if (logger.isInfoEnabled()) {
logger.info("Static JNDI binding: [" + name + "] = [" + obj + "]");
}
this.boundObjects.put(name, obj);
}
/**
* Simple InitialContextFactoryBuilder implementation,
* creating a new SimpleNamingContext instance.
* @see SimpleNamingContext
*/
public InitialContextFactory createInitialContextFactory(Hashtable<?,?> environment) {
if (activated == null && environment != null) {
Object icf = environment.get(Context.INITIAL_CONTEXT_FACTORY);
if (icf != null) {
Class<?> icfClass = null;
if (icf instanceof Class) {
icfClass = (Class<?>) icf;
}
else if (icf instanceof String) {
icfClass = ClassUtils.resolveClassName((String) icf, getClass().getClassLoader());
}
else {
throw new IllegalArgumentException("Invalid value type for environment key [" +
Context.INITIAL_CONTEXT_FACTORY + "]: " + icf.getClass().getName());
}
if (!InitialContextFactory.class.isAssignableFrom(icfClass)) {
throw new IllegalArgumentException(
"Specified class does not implement [" + InitialContextFactory.class.getName() + "]: " + icf);
}
try {
return (InitialContextFactory) icfClass.newInstance();
}
catch (Throwable ex) {
IllegalStateException ise =
new IllegalStateException("Cannot instantiate specified InitialContextFactory: " + icf);
ise.initCause(ex);
throw ise;
}
}
}
// Default case...
return new InitialContextFactory() {
@SuppressWarnings("unchecked")
public Context getInitialContext(Hashtable<?,?> environment) {
return new SimpleNamingContext("", boundObjects, (Hashtable<String, Object>) environment);
}
};
}
}

View File

@@ -0,0 +1,13 @@
/**
*
* The simplest implementation of the JNDI SPI that could possibly work.
*
* <p>Useful for setting up a simple JNDI environment for test suites
* or stand-alone applications. If, for example, JDBC DataSources get bound to the
* same JNDI names as within a Java EE container, both application code and
* configuration can be reused without changes.
*
*/
package org.springframework.mock.jndi;

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2002-2009 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.mock.web;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletInputStream;
import org.springframework.util.Assert;
/**
* Delegating implementation of {@link javax.servlet.ServletInputStream}.
*
* <p>Used by {@link MockHttpServletRequest}; typically not directly
* used for testing application controllers.
*
* @author Juergen Hoeller
* @since 1.0.2
* @see MockHttpServletRequest
*/
public class DelegatingServletInputStream extends ServletInputStream {
private final InputStream sourceStream;
/**
* Create a DelegatingServletInputStream for the given source stream.
* @param sourceStream the source stream (never <code>null</code>)
*/
public DelegatingServletInputStream(InputStream sourceStream) {
Assert.notNull(sourceStream, "Source InputStream must not be null");
this.sourceStream = sourceStream;
}
/**
* Return the underlying source stream (never <code>null</code>).
*/
public final InputStream getSourceStream() {
return this.sourceStream;
}
public int read() throws IOException {
return this.sourceStream.read();
}
public void close() throws IOException {
super.close();
this.sourceStream.close();
}
}

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2009 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.mock.web;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletOutputStream;
import org.springframework.util.Assert;
/**
* Delegating implementation of {@link javax.servlet.ServletOutputStream}.
*
* <p>Used by {@link MockHttpServletResponse}; typically not directly
* used for testing application controllers.
*
* @author Juergen Hoeller
* @since 1.0.2
* @see MockHttpServletResponse
*/
public class DelegatingServletOutputStream extends ServletOutputStream {
private final OutputStream targetStream;
/**
* Create a DelegatingServletOutputStream for the given target stream.
* @param targetStream the target stream (never <code>null</code>)
*/
public DelegatingServletOutputStream(OutputStream targetStream) {
Assert.notNull(targetStream, "Target OutputStream must not be null");
this.targetStream = targetStream;
}
/**
* Return the underlying target stream (never <code>null</code>).
*/
public final OutputStream getTargetStream() {
return this.targetStream;
}
public void write(int b) throws IOException {
this.targetStream.write(b);
}
public void flush() throws IOException {
super.flush();
this.targetStream.flush();
}
public void close() throws IOException {
super.close();
this.targetStream.close();
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Internal helper class that serves as value holder for request headers.
*
* @author Juergen Hoeller
* @author Rick Evans
* @since 2.0.1
*/
class HeaderValueHolder {
private final List<Object> values = new LinkedList<Object>();
public void setValue(Object value) {
this.values.clear();
this.values.add(value);
}
public void addValue(Object value) {
this.values.add(value);
}
public void addValues(Collection<?> values) {
this.values.addAll(values);
}
public void addValueArray(Object values) {
CollectionUtils.mergeArrayIntoCollection(values, this.values);
}
public List<Object> getValues() {
return Collections.unmodifiableList(this.values);
}
public List<String> getStringValues() {
List<String> stringList = new ArrayList<String>(this.values.size());
for (Object value : this.values) {
stringList.add(value.toString());
}
return Collections.unmodifiableList(stringList);
}
public Object getValue() {
return (!this.values.isEmpty() ? this.values.get(0) : null);
}
public String getStringValue() {
return (!this.values.isEmpty() ? this.values.get(0).toString() : null);
}
/**
* Find a HeaderValueHolder by name, ignoring casing.
* @param headers the Map of header names to HeaderValueHolders
* @param name the name of the desired header
* @return the corresponding HeaderValueHolder,
* or <code>null</code> if none found
*/
public static HeaderValueHolder getByName(Map<String, HeaderValueHolder> headers, String name) {
Assert.notNull(name, "Header name must not be null");
for (String headerName : headers.keySet()) {
if (headerName.equalsIgnoreCase(name)) {
return headers.get(headerName);
}
}
return null;
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2002-2009 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.mock.web;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.BodyContent;
/**
* Mock implementation of the {@link javax.servlet.jsp.tagext.BodyContent} class.
*
* <p>Used for testing the web framework; only necessary for testing
* applications when testing custom JSP tags.
*
* @author Juergen Hoeller
* @since 2.5
*/
public class MockBodyContent extends BodyContent {
private final String content;
/**
* Create a MockBodyContent for the given response.
* @param content the body content to expose
* @param response the servlet response to wrap
*/
public MockBodyContent(String content, HttpServletResponse response) {
this(content, response, null);
}
/**
* Create a MockBodyContent for the given response.
* @param content the body content to expose
* @param targetWriter the target Writer to wrap
*/
public MockBodyContent(String content, Writer targetWriter) {
this(content, null, targetWriter);
}
/**
* Create a MockBodyContent for the given response.
* @param content the body content to expose
* @param response the servlet response to wrap
* @param targetWriter the target Writer to wrap
*/
public MockBodyContent(String content, HttpServletResponse response, Writer targetWriter) {
super(adaptJspWriter(targetWriter, response));
this.content = content;
}
private static JspWriter adaptJspWriter(Writer targetWriter, HttpServletResponse response) {
if (targetWriter instanceof JspWriter) {
return (JspWriter) targetWriter;
}
else {
return new MockJspWriter(response, targetWriter);
}
}
public Reader getReader() {
return new StringReader(this.content);
}
public String getString() {
return this.content;
}
public void writeOut(Writer writer) throws IOException {
writer.write(this.content);
}
//---------------------------------------------------------------------
// Delegating implementations of JspWriter's abstract methods
//---------------------------------------------------------------------
public void clear() throws IOException {
getEnclosingWriter().clear();
}
public void clearBuffer() throws IOException {
getEnclosingWriter().clearBuffer();
}
public void close() throws IOException {
getEnclosingWriter().close();
}
public int getRemaining() {
return getEnclosingWriter().getRemaining();
}
public void newLine() throws IOException {
getEnclosingWriter().println();
}
public void write(char value[], int offset, int length) throws IOException {
getEnclosingWriter().write(value, offset, length);
}
public void print(boolean value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(char value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(char[] value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(double value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(float value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(int value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(long value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(Object value) throws IOException {
getEnclosingWriter().print(value);
}
public void print(String value) throws IOException {
getEnclosingWriter().print(value);
}
public void println() throws IOException {
getEnclosingWriter().println();
}
public void println(boolean value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(char value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(char[] value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(double value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(float value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(int value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(long value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(Object value) throws IOException {
getEnclosingWriter().println(value);
}
public void println(String value) throws IOException {
getEnclosingWriter().println(value);
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2002-2009 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.mock.web;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.Expression;
import javax.servlet.jsp.el.ExpressionEvaluator;
import javax.servlet.jsp.el.FunctionMapper;
import javax.servlet.jsp.el.VariableResolver;
import org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager;
/**
* Mock implementation of the JSP 2.0
* {@link javax.servlet.jsp.el.ExpressionEvaluator} interface, delegating to the
* Jakarta JSTL ExpressionEvaluatorManager.
* <p>
* Used for testing the web framework; only necessary for testing applications
* when testing custom JSP tags.
* <p>
* Note that the Jakarta JSTL implementation (jstl.jar, standard.jar) has to be
* available on the class path to use this expression evaluator.
*
* @author Juergen Hoeller
* @since 1.1.5
* @see org.apache.taglibs.standard.lang.support.ExpressionEvaluatorManager
*/
@SuppressWarnings("deprecation")
public class MockExpressionEvaluator extends ExpressionEvaluator {
private final PageContext pageContext;
/**
* Create a new MockExpressionEvaluator for the given PageContext.
*
* @param pageContext the JSP PageContext to run in
*/
public MockExpressionEvaluator(PageContext pageContext) {
this.pageContext = pageContext;
}
@SuppressWarnings("rawtypes")
public Expression parseExpression(final String expression, final Class expectedType,
final FunctionMapper functionMapper) throws ELException {
return new Expression() {
public Object evaluate(VariableResolver variableResolver) throws ELException {
return doEvaluate(expression, expectedType, functionMapper);
}
};
}
@SuppressWarnings("rawtypes")
public Object evaluate(String expression, Class expectedType, VariableResolver variableResolver,
FunctionMapper functionMapper) throws ELException {
if (variableResolver != null) {
throw new IllegalArgumentException("Custom VariableResolver not supported");
}
return doEvaluate(expression, expectedType, functionMapper);
}
@SuppressWarnings("rawtypes")
protected Object doEvaluate(String expression, Class expectedType, FunctionMapper functionMapper)
throws ELException {
if (functionMapper != null) {
throw new IllegalArgumentException("Custom FunctionMapper not supported");
}
try {
return ExpressionEvaluatorManager.evaluate("JSP EL expression", expression, expectedType, this.pageContext);
}
catch (JspException ex) {
throw new ELException("Parsing of JSP EL expression \"" + expression + "\" failed", ex);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2009 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.mock.web;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.servlet.FilterConfig} interface.
*
* <p>Used for testing the web framework; also useful for testing
* custom {@link javax.servlet.Filter} implementations.
*
* @author Juergen Hoeller
* @since 2.0.3
* @see MockFilterConfig
* @see PassThroughFilterChain
*/
public class MockFilterChain implements FilterChain {
private ServletRequest request;
private ServletResponse response;
/**
* Records the request and response.
*/
public void doFilter(ServletRequest request, ServletResponse response) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
if (this.request != null) {
throw new IllegalStateException("This FilterChain has already been called!");
}
this.request = request;
this.response = response;
}
/**
* Return the request that {@link #doFilter} has been called with.
*/
public ServletRequest getRequest() {
return this.request;
}
/**
* Return the response that {@link #doFilter} has been called with.
*/
public ServletResponse getResponse() {
return this.response;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2009 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.mock.web;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.servlet.FilterConfig} interface.
*
* <p>Used for testing the web framework; also useful for testing
* custom {@link javax.servlet.Filter} implementations.
*
* @author Juergen Hoeller
* @since 1.0.2
* @see MockFilterChain
* @see PassThroughFilterChain
*/
public class MockFilterConfig implements FilterConfig {
private final ServletContext servletContext;
private final String filterName;
private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
/**
* Create a new MockFilterConfig with a default {@link MockServletContext}.
*/
public MockFilterConfig() {
this(null, "");
}
/**
* Create a new MockFilterConfig with a default {@link MockServletContext}.
* @param filterName the name of the filter
*/
public MockFilterConfig(String filterName) {
this(null, filterName);
}
/**
* Create a new MockFilterConfig.
* @param servletContext the ServletContext that the servlet runs in
*/
public MockFilterConfig(ServletContext servletContext) {
this(servletContext, "");
}
/**
* Create a new MockFilterConfig.
* @param servletContext the ServletContext that the servlet runs in
* @param filterName the name of the filter
*/
public MockFilterConfig(ServletContext servletContext, String filterName) {
this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
this.filterName = filterName;
}
public String getFilterName() {
return filterName;
}
public ServletContext getServletContext() {
return servletContext;
}
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
this.initParameters.put(name, value);
}
public String getInitParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
return this.initParameters.get(name);
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
}

View File

@@ -0,0 +1,904 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
/**
* Mock implementation of the {@link javax.servlet.http.HttpServletRequest} interface.
*
* <p>Compatible with Servlet 2.5 and partially with Servlet 3.0 (notable exceptions:
* the <code>getPart(s)</code> and <code>startAsync</code> families of methods).
*
* @author Juergen Hoeller
* @author Rod Johnson
* @author Rick Evans
* @author Mark Fisher
* @since 1.0.2
*/
public class MockHttpServletRequest implements HttpServletRequest {
/**
* The default protocol: 'http'.
*/
public static final String DEFAULT_PROTOCOL = "http";
/**
* The default server address: '127.0.0.1'.
*/
public static final String DEFAULT_SERVER_ADDR = "127.0.0.1";
/**
* The default server name: 'localhost'.
*/
public static final String DEFAULT_SERVER_NAME = "localhost";
/**
* The default server port: '80'.
*/
public static final int DEFAULT_SERVER_PORT = 80;
/**
* The default remote address: '127.0.0.1'.
*/
public static final String DEFAULT_REMOTE_ADDR = "127.0.0.1";
/**
* The default remote host: 'localhost'.
*/
public static final String DEFAULT_REMOTE_HOST = "localhost";
private static final String CONTENT_TYPE_HEADER = "Content-Type";
private static final String CHARSET_PREFIX = "charset=";
private boolean active = true;
// ---------------------------------------------------------------------
// ServletRequest properties
// ---------------------------------------------------------------------
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private String characterEncoding;
private byte[] content;
private String contentType;
private final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>(16);
private String protocol = DEFAULT_PROTOCOL;
private String scheme = DEFAULT_PROTOCOL;
private String serverName = DEFAULT_SERVER_NAME;
private int serverPort = DEFAULT_SERVER_PORT;
private String remoteAddr = DEFAULT_REMOTE_ADDR;
private String remoteHost = DEFAULT_REMOTE_HOST;
/** List of locales in descending order */
private final List<Locale> locales = new LinkedList<Locale>();
private boolean secure = false;
private final ServletContext servletContext;
private int remotePort = DEFAULT_SERVER_PORT;
private String localName = DEFAULT_SERVER_NAME;
private String localAddr = DEFAULT_SERVER_ADDR;
private int localPort = DEFAULT_SERVER_PORT;
// ---------------------------------------------------------------------
// HttpServletRequest properties
// ---------------------------------------------------------------------
private String authType;
private Cookie[] cookies;
private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<HeaderValueHolder>();
private String method;
private String pathInfo;
private String contextPath = "";
private String queryString;
private String remoteUser;
private final Set<String> userRoles = new HashSet<String>();
private Principal userPrincipal;
private String requestedSessionId;
private String requestURI;
private String servletPath = "";
private HttpSession session;
private boolean requestedSessionIdValid = true;
private boolean requestedSessionIdFromCookie = true;
private boolean requestedSessionIdFromURL = false;
// ---------------------------------------------------------------------
// Constructors
// ---------------------------------------------------------------------
/**
* Create a new MockHttpServletRequest with a default
* {@link MockServletContext}.
* @see MockServletContext
*/
public MockHttpServletRequest() {
this(null, "", "");
}
/**
* Create a new MockHttpServletRequest with a default
* {@link MockServletContext}.
* @param method the request method (may be <code>null</code>)
* @param requestURI the request URI (may be <code>null</code>)
* @see #setMethod
* @see #setRequestURI
* @see MockServletContext
*/
public MockHttpServletRequest(String method, String requestURI) {
this(null, method, requestURI);
}
/**
* Create a new MockHttpServletRequest.
* @param servletContext the ServletContext that the request runs in (may be
* <code>null</code> to use a default MockServletContext)
* @see MockServletContext
*/
public MockHttpServletRequest(ServletContext servletContext) {
this(servletContext, "", "");
}
/**
* Create a new MockHttpServletRequest.
* @param servletContext the ServletContext that the request runs in (may be
* <code>null</code> to use a default MockServletContext)
* @param method the request method (may be <code>null</code>)
* @param requestURI the request URI (may be <code>null</code>)
* @see #setMethod
* @see #setRequestURI
* @see MockServletContext
*/
public MockHttpServletRequest(ServletContext servletContext, String method, String requestURI) {
this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
this.method = method;
this.requestURI = requestURI;
this.locales.add(Locale.ENGLISH);
}
// ---------------------------------------------------------------------
// Lifecycle methods
// ---------------------------------------------------------------------
/**
* Return the ServletContext that this request is associated with. (Not
* available in the standard HttpServletRequest interface for some reason.)
*/
public ServletContext getServletContext() {
return this.servletContext;
}
/**
* Return whether this request is still active (that is, not completed yet).
*/
public boolean isActive() {
return this.active;
}
/**
* Mark this request as completed, keeping its state.
*/
public void close() {
this.active = false;
}
/**
* Invalidate this request, clearing its state.
*/
public void invalidate() {
close();
clearAttributes();
}
/**
* Check whether this request is still active (that is, not completed yet),
* throwing an IllegalStateException if not active anymore.
*/
protected void checkActive() throws IllegalStateException {
if (!this.active) {
throw new IllegalStateException("Request is not active anymore");
}
}
// ---------------------------------------------------------------------
// ServletRequest interface
// ---------------------------------------------------------------------
public Object getAttribute(String name) {
checkActive();
return this.attributes.get(name);
}
public Enumeration<String> getAttributeNames() {
checkActive();
return new Vector<String>(this.attributes.keySet()).elements();
}
public String getCharacterEncoding() {
return this.characterEncoding;
}
public void setCharacterEncoding(String characterEncoding) {
this.characterEncoding = characterEncoding;
updateContentTypeHeader();
}
private void updateContentTypeHeader() {
if (this.contentType != null) {
StringBuilder sb = new StringBuilder(this.contentType);
if (!this.contentType.toLowerCase().contains(CHARSET_PREFIX) && this.characterEncoding != null) {
sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding);
}
doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true);
}
}
public void setContent(byte[] content) {
this.content = content;
}
public int getContentLength() {
return (this.content != null ? this.content.length : -1);
}
public void setContentType(String contentType) {
this.contentType = contentType;
if (contentType != null) {
int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX);
if (charsetIndex != -1) {
String encoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length());
this.characterEncoding = encoding;
}
updateContentTypeHeader();
}
}
public String getContentType() {
return this.contentType;
}
public ServletInputStream getInputStream() {
if (this.content != null) {
return new DelegatingServletInputStream(new ByteArrayInputStream(this.content));
}
else {
return null;
}
}
/**
* Set a single value for the specified HTTP parameter.
* <p>
* If there are already one or more values registered for the given
* parameter name, they will be replaced.
*/
public void setParameter(String name, String value) {
setParameter(name, new String[] { value });
}
/**
* Set an array of values for the specified HTTP parameter.
* <p>
* If there are already one or more values registered for the given
* parameter name, they will be replaced.
*/
public void setParameter(String name, String[] values) {
Assert.notNull(name, "Parameter name must not be null");
this.parameters.put(name, values);
}
/**
* Sets all provided parameters <emphasis>replacing</emphasis> any existing
* values for the provided parameter names. To add without replacing
* existing values, use {@link #addParameters(java.util.Map)}.
*/
@SuppressWarnings("rawtypes")
public void setParameters(Map params) {
Assert.notNull(params, "Parameter map must not be null");
for (Object key : params.keySet()) {
Assert.isInstanceOf(String.class, key, "Parameter map key must be of type [" + String.class.getName() + "]");
Object value = params.get(key);
if (value instanceof String) {
this.setParameter((String) key, (String) value);
}
else if (value instanceof String[]) {
this.setParameter((String) key, (String[]) value);
}
else {
throw new IllegalArgumentException("Parameter map value must be single value " + " or array of type ["
+ String.class.getName() + "]");
}
}
}
/**
* Add a single value for the specified HTTP parameter.
* <p>
* If there are already one or more values registered for the given
* parameter name, the given value will be added to the end of the list.
*/
public void addParameter(String name, String value) {
addParameter(name, new String[] { value });
}
/**
* Add an array of values for the specified HTTP parameter.
* <p>
* If there are already one or more values registered for the given
* parameter name, the given values will be added to the end of the list.
*/
public void addParameter(String name, String[] values) {
Assert.notNull(name, "Parameter name must not be null");
String[] oldArr = this.parameters.get(name);
if (oldArr != null) {
String[] newArr = new String[oldArr.length + values.length];
System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
System.arraycopy(values, 0, newArr, oldArr.length, values.length);
this.parameters.put(name, newArr);
}
else {
this.parameters.put(name, values);
}
}
/**
* Adds all provided parameters <emphasis>without</emphasis> replacing any
* existing values. To replace existing values, use
* {@link #setParameters(java.util.Map)}.
*/
@SuppressWarnings("rawtypes")
public void addParameters(Map params) {
Assert.notNull(params, "Parameter map must not be null");
for (Object key : params.keySet()) {
Assert.isInstanceOf(String.class, key, "Parameter map key must be of type [" + String.class.getName() + "]");
Object value = params.get(key);
if (value instanceof String) {
this.addParameter((String) key, (String) value);
}
else if (value instanceof String[]) {
this.addParameter((String) key, (String[]) value);
}
else {
throw new IllegalArgumentException("Parameter map value must be single value " + " or array of type ["
+ String.class.getName() + "]");
}
}
}
/**
* Remove already registered values for the specified HTTP parameter, if
* any.
*/
public void removeParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
this.parameters.remove(name);
}
/**
* Removes all existing parameters.
*/
public void removeAllParameters() {
this.parameters.clear();
}
public String getParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
String[] arr = this.parameters.get(name);
return (arr != null && arr.length > 0 ? arr[0] : null);
}
public Enumeration<String> getParameterNames() {
return Collections.enumeration(this.parameters.keySet());
}
public String[] getParameterValues(String name) {
Assert.notNull(name, "Parameter name must not be null");
return this.parameters.get(name);
}
public Map<String, String[]> getParameterMap() {
return Collections.unmodifiableMap(this.parameters);
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getProtocol() {
return this.protocol;
}
public void setScheme(String scheme) {
this.scheme = scheme;
}
public String getScheme() {
return this.scheme;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getServerName() {
return this.serverName;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public int getServerPort() {
return this.serverPort;
}
public BufferedReader getReader() throws UnsupportedEncodingException {
if (this.content != null) {
InputStream sourceStream = new ByteArrayInputStream(this.content);
Reader sourceReader = (this.characterEncoding != null) ? new InputStreamReader(sourceStream,
this.characterEncoding) : new InputStreamReader(sourceStream);
return new BufferedReader(sourceReader);
}
else {
return null;
}
}
public void setRemoteAddr(String remoteAddr) {
this.remoteAddr = remoteAddr;
}
public String getRemoteAddr() {
return this.remoteAddr;
}
public void setRemoteHost(String remoteHost) {
this.remoteHost = remoteHost;
}
public String getRemoteHost() {
return this.remoteHost;
}
public void setAttribute(String name, Object value) {
checkActive();
Assert.notNull(name, "Attribute name must not be null");
if (value != null) {
this.attributes.put(name, value);
}
else {
this.attributes.remove(name);
}
}
public void removeAttribute(String name) {
checkActive();
Assert.notNull(name, "Attribute name must not be null");
this.attributes.remove(name);
}
/**
* Clear all of this request's attributes.
*/
public void clearAttributes() {
this.attributes.clear();
}
/**
* Add a new preferred locale, before any existing locales.
*/
public void addPreferredLocale(Locale locale) {
Assert.notNull(locale, "Locale must not be null");
this.locales.add(0, locale);
}
public Locale getLocale() {
return this.locales.get(0);
}
public Enumeration<Locale> getLocales() {
return Collections.enumeration(this.locales);
}
public void setSecure(boolean secure) {
this.secure = secure;
}
public boolean isSecure() {
return this.secure;
}
public RequestDispatcher getRequestDispatcher(String path) {
return new MockRequestDispatcher(path);
}
public String getRealPath(String path) {
return this.servletContext.getRealPath(path);
}
public void setRemotePort(int remotePort) {
this.remotePort = remotePort;
}
public int getRemotePort() {
return this.remotePort;
}
public void setLocalName(String localName) {
this.localName = localName;
}
public String getLocalName() {
return this.localName;
}
public void setLocalAddr(String localAddr) {
this.localAddr = localAddr;
}
public String getLocalAddr() {
return this.localAddr;
}
public void setLocalPort(int localPort) {
this.localPort = localPort;
}
public int getLocalPort() {
return this.localPort;
}
// ---------------------------------------------------------------------
// HttpServletRequest interface
// ---------------------------------------------------------------------
public void setAuthType(String authType) {
this.authType = authType;
}
public String getAuthType() {
return this.authType;
}
public void setCookies(Cookie... cookies) {
this.cookies = cookies;
}
public Cookie[] getCookies() {
return this.cookies;
}
/**
* Add a header entry for the given name.
* <p>If there was no entry for that header name before, the value will be used
* as-is. In case of an existing entry, a String array will be created,
* adding the given value (more specifically, its toString representation)
* as further element.
* <p>Multiple values can only be stored as list of Strings, following the
* Servlet spec (see <code>getHeaders</code> accessor). As alternative to
* repeated <code>addHeader</code> calls for individual elements, you can
* use a single call with an entire array or Collection of values as
* parameter.
* @see #getHeaderNames
* @see #getHeader
* @see #getHeaders
* @see #getDateHeader
* @see #getIntHeader
*/
public void addHeader(String name, Object value) {
if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) {
setContentType((String) value);
return;
}
doAddHeaderValue(name, value, false);
}
@SuppressWarnings("rawtypes")
private void doAddHeaderValue(String name, Object value, boolean replace) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
Assert.notNull(value, "Header value must not be null");
if (header == null || replace) {
header = new HeaderValueHolder();
this.headers.put(name, header);
}
if (value instanceof Collection) {
header.addValues((Collection) value);
}
else if (value.getClass().isArray()) {
header.addValueArray(value);
}
else {
header.addValue(value);
}
}
public long getDateHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
Object value = (header != null ? header.getValue() : null);
if (value instanceof Date) {
return ((Date) value).getTime();
}
else if (value instanceof Number) {
return ((Number) value).longValue();
}
else if (value != null) {
throw new IllegalArgumentException("Value for header '" + name + "' is neither a Date nor a Number: "
+ value);
}
else {
return -1L;
}
}
public int getIntHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
Object value = (header != null ? header.getValue() : null);
if (value instanceof Number) {
return ((Number) value).intValue();
}
else if (value instanceof String) {
return Integer.parseInt((String) value);
}
else if (value != null) {
throw new NumberFormatException("Value for header '" + name + "' is not a Number: " + value);
}
else {
return -1;
}
}
public String getHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
return (header != null ? header.getStringValue() : null);
}
public Enumeration<String> getHeaders(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
return Collections.enumeration(header != null ? header.getStringValues() : new LinkedList<String>());
}
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(this.headers.keySet());
}
public void setMethod(String method) {
this.method = method;
}
public String getMethod() {
return this.method;
}
public void setPathInfo(String pathInfo) {
this.pathInfo = pathInfo;
}
public String getPathInfo() {
return this.pathInfo;
}
public String getPathTranslated() {
return (this.pathInfo != null ? getRealPath(this.pathInfo) : null);
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public String getContextPath() {
return this.contextPath;
}
public void setQueryString(String queryString) {
this.queryString = queryString;
}
public String getQueryString() {
return this.queryString;
}
public void setRemoteUser(String remoteUser) {
this.remoteUser = remoteUser;
}
public String getRemoteUser() {
return this.remoteUser;
}
public void addUserRole(String role) {
this.userRoles.add(role);
}
public boolean isUserInRole(String role) {
return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext &&
((MockServletContext) this.servletContext).getDeclaredRoles().contains(role)));
}
public void setUserPrincipal(Principal userPrincipal) {
this.userPrincipal = userPrincipal;
}
public Principal getUserPrincipal() {
return this.userPrincipal;
}
public void setRequestedSessionId(String requestedSessionId) {
this.requestedSessionId = requestedSessionId;
}
public String getRequestedSessionId() {
return this.requestedSessionId;
}
public void setRequestURI(String requestURI) {
this.requestURI = requestURI;
}
public String getRequestURI() {
return this.requestURI;
}
public StringBuffer getRequestURL() {
StringBuffer url = new StringBuffer(this.scheme);
url.append("://").append(this.serverName).append(':').append(this.serverPort);
url.append(getRequestURI());
return url;
}
public void setServletPath(String servletPath) {
this.servletPath = servletPath;
}
public String getServletPath() {
return this.servletPath;
}
public void setSession(HttpSession session) {
this.session = session;
if (session instanceof MockHttpSession) {
MockHttpSession mockSession = ((MockHttpSession) session);
mockSession.access();
}
}
public HttpSession getSession(boolean create) {
checkActive();
// Reset session if invalidated.
if (this.session instanceof MockHttpSession && ((MockHttpSession) this.session).isInvalid()) {
this.session = null;
}
// Create new session if necessary.
if (this.session == null && create) {
this.session = new MockHttpSession(this.servletContext);
}
return this.session;
}
public HttpSession getSession() {
return getSession(true);
}
public void setRequestedSessionIdValid(boolean requestedSessionIdValid) {
this.requestedSessionIdValid = requestedSessionIdValid;
}
public boolean isRequestedSessionIdValid() {
return this.requestedSessionIdValid;
}
public void setRequestedSessionIdFromCookie(boolean requestedSessionIdFromCookie) {
this.requestedSessionIdFromCookie = requestedSessionIdFromCookie;
}
public boolean isRequestedSessionIdFromCookie() {
return this.requestedSessionIdFromCookie;
}
public void setRequestedSessionIdFromURL(boolean requestedSessionIdFromURL) {
this.requestedSessionIdFromURL = requestedSessionIdFromURL;
}
public boolean isRequestedSessionIdFromURL() {
return this.requestedSessionIdFromURL;
}
public boolean isRequestedSessionIdFromUrl() {
return isRequestedSessionIdFromURL();
}
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
return (this.userPrincipal != null && this.remoteUser != null && this.authType != null);
}
public void login(String username, String password) throws ServletException {
throw new ServletException("Username-password authentication not supported - override the login method");
}
public void logout() throws ServletException {
this.userPrincipal = null;
this.remoteUser = null;
this.authType = null;
}
}

View File

@@ -0,0 +1,602 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.web.util.WebUtils;
/**
* Mock implementation of the {@link javax.servlet.http.HttpServletResponse} interface.
*
* <p>Compatible with Servlet 2.5 as well as Servlet 3.0.
*
* @author Juergen Hoeller
* @author Rod Johnson
* @since 1.0.2
*/
public class MockHttpServletResponse implements HttpServletResponse {
private static final String CHARSET_PREFIX = "charset=";
private static final String CONTENT_TYPE_HEADER = "Content-Type";
private static final String CONTENT_LENGTH_HEADER = "Content-Length";
//---------------------------------------------------------------------
// ServletResponse properties
//---------------------------------------------------------------------
private boolean outputStreamAccessAllowed = true;
private boolean writerAccessAllowed = true;
private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
private boolean charset = false;
private final ByteArrayOutputStream content = new ByteArrayOutputStream();
private final ServletOutputStream outputStream = new ResponseServletOutputStream(this.content);
private PrintWriter writer;
private int contentLength = 0;
private String contentType;
private int bufferSize = 4096;
private boolean committed;
private Locale locale = Locale.getDefault();
//---------------------------------------------------------------------
// HttpServletResponse properties
//---------------------------------------------------------------------
private final List<Cookie> cookies = new ArrayList<Cookie>();
private final Map<String, HeaderValueHolder> headers = new LinkedCaseInsensitiveMap<HeaderValueHolder>();
private int status = HttpServletResponse.SC_OK;
private String errorMessage;
private String redirectedUrl;
private String forwardedUrl;
private final List<String> includedUrls = new ArrayList<String>();
//---------------------------------------------------------------------
// ServletResponse interface
//---------------------------------------------------------------------
/**
* Set whether {@link #getOutputStream()} access is allowed.
* <p>Default is <code>true</code>.
*/
public void setOutputStreamAccessAllowed(boolean outputStreamAccessAllowed) {
this.outputStreamAccessAllowed = outputStreamAccessAllowed;
}
/**
* Return whether {@link #getOutputStream()} access is allowed.
*/
public boolean isOutputStreamAccessAllowed() {
return this.outputStreamAccessAllowed;
}
/**
* Set whether {@link #getWriter()} access is allowed.
* <p>Default is <code>true</code>.
*/
public void setWriterAccessAllowed(boolean writerAccessAllowed) {
this.writerAccessAllowed = writerAccessAllowed;
}
/**
* Return whether {@link #getOutputStream()} access is allowed.
*/
public boolean isWriterAccessAllowed() {
return this.writerAccessAllowed;
}
public void setCharacterEncoding(String characterEncoding) {
this.characterEncoding = characterEncoding;
this.charset = true;
updateContentTypeHeader();
}
private void updateContentTypeHeader() {
if (this.contentType != null) {
StringBuilder sb = new StringBuilder(this.contentType);
if (this.contentType.toLowerCase().indexOf(CHARSET_PREFIX) == -1 && this.charset) {
sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding);
}
doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true);
}
}
public String getCharacterEncoding() {
return this.characterEncoding;
}
public ServletOutputStream getOutputStream() {
if (!this.outputStreamAccessAllowed) {
throw new IllegalStateException("OutputStream access not allowed");
}
return this.outputStream;
}
public PrintWriter getWriter() throws UnsupportedEncodingException {
if (!this.writerAccessAllowed) {
throw new IllegalStateException("Writer access not allowed");
}
if (this.writer == null) {
Writer targetWriter = (this.characterEncoding != null ?
new OutputStreamWriter(this.content, this.characterEncoding) : new OutputStreamWriter(this.content));
this.writer = new ResponsePrintWriter(targetWriter);
}
return this.writer;
}
public byte[] getContentAsByteArray() {
flushBuffer();
return this.content.toByteArray();
}
public String getContentAsString() throws UnsupportedEncodingException {
flushBuffer();
return (this.characterEncoding != null) ?
this.content.toString(this.characterEncoding) : this.content.toString();
}
public void setContentLength(int contentLength) {
this.contentLength = contentLength;
doAddHeaderValue(CONTENT_LENGTH_HEADER, contentLength, true);
}
public int getContentLength() {
return this.contentLength;
}
public void setContentType(String contentType) {
this.contentType = contentType;
if (contentType != null) {
int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX);
if (charsetIndex != -1) {
String encoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length());
this.characterEncoding = encoding;
this.charset = true;
}
updateContentTypeHeader();
}
}
public String getContentType() {
return this.contentType;
}
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
public int getBufferSize() {
return this.bufferSize;
}
public void flushBuffer() {
setCommitted(true);
}
public void resetBuffer() {
if (isCommitted()) {
throw new IllegalStateException("Cannot reset buffer - response is already committed");
}
this.content.reset();
}
private void setCommittedIfBufferSizeExceeded() {
int bufSize = getBufferSize();
if (bufSize > 0 && this.content.size() > bufSize) {
setCommitted(true);
}
}
public void setCommitted(boolean committed) {
this.committed = committed;
}
public boolean isCommitted() {
return this.committed;
}
public void reset() {
resetBuffer();
this.characterEncoding = null;
this.contentLength = 0;
this.contentType = null;
this.locale = null;
this.cookies.clear();
this.headers.clear();
this.status = HttpServletResponse.SC_OK;
this.errorMessage = null;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public Locale getLocale() {
return this.locale;
}
//---------------------------------------------------------------------
// HttpServletResponse interface
//---------------------------------------------------------------------
public void addCookie(Cookie cookie) {
Assert.notNull(cookie, "Cookie must not be null");
this.cookies.add(cookie);
}
public Cookie[] getCookies() {
return this.cookies.toArray(new Cookie[this.cookies.size()]);
}
public Cookie getCookie(String name) {
Assert.notNull(name, "Cookie name must not be null");
for (Cookie cookie : this.cookies) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
return null;
}
public boolean containsHeader(String name) {
return (HeaderValueHolder.getByName(this.headers, name) != null);
}
/**
* Return the names of all specified headers as a Set of Strings.
* <p>As of Servlet 3.0, this method is also defined HttpServletResponse.
* @return the <code>Set</code> of header name <code>Strings</code>, or an empty <code>Set</code> if none
*/
public Set<String> getHeaderNames() {
return this.headers.keySet();
}
/**
* Return the primary value for the given header as a String, if any.
* Will return the first value in case of multiple values.
* <p>As of Servlet 3.0, this method is also defined HttpServletResponse.
* As of Spring 3.1, it returns a stringified value for Servlet 3.0 compatibility.
* Consider using {@link #getHeaderValue(String)} for raw Object access.
* @param name the name of the header
* @return the associated header value, or <code>null<code> if none
*/
public String getHeader(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
return (header != null ? header.getStringValue() : null);
}
/**
* Return all values for the given header as a List of Strings.
* <p>As of Servlet 3.0, this method is also defined HttpServletResponse.
* As of Spring 3.1, it returns a List of stringified values for Servlet 3.0 compatibility.
* Consider using {@link #getHeaderValues(String)} for raw Object access.
* @param name the name of the header
* @return the associated header values, or an empty List if none
*/
public List<String> getHeaders(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
if (header != null) {
return header.getStringValues();
}
else {
return Collections.emptyList();
}
}
/**
* Return the primary value for the given header, if any.
* <p>Will return the first value in case of multiple values.
* @param name the name of the header
* @return the associated header value, or <code>null<code> if none
*/
public Object getHeaderValue(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
return (header != null ? header.getValue() : null);
}
/**
* Return all values for the given header as a List of value objects.
* @param name the name of the header
* @return the associated header values, or an empty List if none
*/
public List<Object> getHeaderValues(String name) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
if (header != null) {
return header.getValues();
}
else {
return Collections.emptyList();
}
}
/**
* The default implementation returns the given URL String as-is.
* <p>Can be overridden in subclasses, appending a session id or the like.
*/
public String encodeURL(String url) {
return url;
}
/**
* The default implementation delegates to {@link #encodeURL},
* returning the given URL String as-is.
* <p>Can be overridden in subclasses, appending a session id or the like
* in a redirect-specific fashion. For general URL encoding rules,
* override the common {@link #encodeURL} method instead, appyling
* to redirect URLs as well as to general URLs.
*/
public String encodeRedirectURL(String url) {
return encodeURL(url);
}
public String encodeUrl(String url) {
return encodeURL(url);
}
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
public void sendError(int status, String errorMessage) throws IOException {
if (isCommitted()) {
throw new IllegalStateException("Cannot set error status - response is already committed");
}
this.status = status;
this.errorMessage = errorMessage;
setCommitted(true);
}
public void sendError(int status) throws IOException {
if (isCommitted()) {
throw new IllegalStateException("Cannot set error status - response is already committed");
}
this.status = status;
setCommitted(true);
}
public void sendRedirect(String url) throws IOException {
if (isCommitted()) {
throw new IllegalStateException("Cannot send redirect - response is already committed");
}
Assert.notNull(url, "Redirect URL must not be null");
this.redirectedUrl = url;
setCommitted(true);
}
public String getRedirectedUrl() {
return this.redirectedUrl;
}
public void setDateHeader(String name, long value) {
setHeaderValue(name, value);
}
public void addDateHeader(String name, long value) {
addHeaderValue(name, value);
}
public void setHeader(String name, String value) {
setHeaderValue(name, value);
}
public void addHeader(String name, String value) {
addHeaderValue(name, value);
}
public void setIntHeader(String name, int value) {
setHeaderValue(name, value);
}
public void addIntHeader(String name, int value) {
addHeaderValue(name, value);
}
private void setHeaderValue(String name, Object value) {
if (setSpecialHeader(name, value)) {
return;
}
doAddHeaderValue(name, value, true);
}
private void addHeaderValue(String name, Object value) {
if (setSpecialHeader(name, value)) {
return;
}
doAddHeaderValue(name, value, false);
}
private boolean setSpecialHeader(String name, Object value) {
if (CONTENT_TYPE_HEADER.equalsIgnoreCase(name)) {
setContentType((String) value);
return true;
}
else if (CONTENT_LENGTH_HEADER.equalsIgnoreCase(name)) {
setContentLength(Integer.parseInt((String) value));
return true;
}
else {
return false;
}
}
private void doAddHeaderValue(String name, Object value, boolean replace) {
HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
Assert.notNull(value, "Header value must not be null");
if (header == null) {
header = new HeaderValueHolder();
this.headers.put(name, header);
}
if (replace) {
header.setValue(value);
}
else {
header.addValue(value);
}
}
public void setStatus(int status) {
this.status = status;
}
public void setStatus(int status, String errorMessage) {
this.status = status;
this.errorMessage = errorMessage;
}
public int getStatus() {
return this.status;
}
public String getErrorMessage() {
return this.errorMessage;
}
//---------------------------------------------------------------------
// Methods for MockRequestDispatcher
//---------------------------------------------------------------------
public void setForwardedUrl(String forwardedUrl) {
this.forwardedUrl = forwardedUrl;
}
public String getForwardedUrl() {
return this.forwardedUrl;
}
public void setIncludedUrl(String includedUrl) {
this.includedUrls.clear();
if (includedUrl != null) {
this.includedUrls.add(includedUrl);
}
}
public String getIncludedUrl() {
int count = this.includedUrls.size();
if (count > 1) {
throw new IllegalStateException(
"More than 1 URL included - check getIncludedUrls instead: " + this.includedUrls);
}
return (count == 1 ? this.includedUrls.get(0) : null);
}
public void addIncludedUrl(String includedUrl) {
Assert.notNull(includedUrl, "Included URL must not be null");
this.includedUrls.add(includedUrl);
}
public List<String> getIncludedUrls() {
return this.includedUrls;
}
/**
* Inner class that adapts the ServletOutputStream to mark the
* response as committed once the buffer size is exceeded.
*/
private class ResponseServletOutputStream extends DelegatingServletOutputStream {
public ResponseServletOutputStream(OutputStream out) {
super(out);
}
public void write(int b) throws IOException {
super.write(b);
super.flush();
setCommittedIfBufferSizeExceeded();
}
public void flush() throws IOException {
super.flush();
setCommitted(true);
}
}
/**
* Inner class that adapts the PrintWriter to mark the
* response as committed once the buffer size is exceeded.
*/
private class ResponsePrintWriter extends PrintWriter {
public ResponsePrintWriter(Writer out) {
super(out, true);
}
public void write(char buf[], int off, int len) {
super.write(buf, off, len);
super.flush();
setCommittedIfBufferSizeExceeded();
}
public void write(String s, int off, int len) {
super.write(s, off, len);
super.flush();
setCommittedIfBufferSizeExceeded();
}
public void write(int c) {
super.write(c);
super.flush();
setCommittedIfBufferSizeExceeded();
}
public void flush() {
super.flush();
setCommitted(true);
}
}
}

View File

@@ -0,0 +1,245 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Vector;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionContext;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.servlet.http.HttpSession} interface.
*
* <p>Compatible with Servlet 2.5 as well as Servlet 3.0.
*
* @author Juergen Hoeller
* @author Rod Johnson
* @author Mark Fisher
* @since 1.0.2
*/
@SuppressWarnings("deprecation")
public class MockHttpSession implements HttpSession {
private static int nextId = 1;
private final String id;
private final long creationTime = System.currentTimeMillis();
private int maxInactiveInterval;
private long lastAccessedTime = System.currentTimeMillis();
private final ServletContext servletContext;
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private boolean invalid = false;
private boolean isNew = true;
/**
* Create a new MockHttpSession with a default {@link MockServletContext}.
*
* @see MockServletContext
*/
public MockHttpSession() {
this(null);
}
/**
* Create a new MockHttpSession.
*
* @param servletContext the ServletContext that the session runs in
*/
public MockHttpSession(ServletContext servletContext) {
this(servletContext, null);
}
/**
* Create a new MockHttpSession.
*
* @param servletContext the ServletContext that the session runs in
* @param id a unique identifier for this session
*/
public MockHttpSession(ServletContext servletContext, String id) {
this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
this.id = (id != null ? id : Integer.toString(nextId++));
}
public long getCreationTime() {
return this.creationTime;
}
public String getId() {
return this.id;
}
public void access() {
this.lastAccessedTime = System.currentTimeMillis();
this.isNew = false;
}
public long getLastAccessedTime() {
return this.lastAccessedTime;
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void setMaxInactiveInterval(int interval) {
this.maxInactiveInterval = interval;
}
public int getMaxInactiveInterval() {
return this.maxInactiveInterval;
}
public HttpSessionContext getSessionContext() {
throw new UnsupportedOperationException("getSessionContext");
}
public Object getAttribute(String name) {
Assert.notNull(name, "Attribute name must not be null");
return this.attributes.get(name);
}
public Object getValue(String name) {
return getAttribute(name);
}
public Enumeration<String> getAttributeNames() {
return new Vector<String>(this.attributes.keySet()).elements();
}
public String[] getValueNames() {
return this.attributes.keySet().toArray(new String[this.attributes.size()]);
}
public void setAttribute(String name, Object value) {
Assert.notNull(name, "Attribute name must not be null");
if (value != null) {
this.attributes.put(name, value);
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
}
}
else {
removeAttribute(name);
}
}
public void putValue(String name, Object value) {
setAttribute(name, value);
}
public void removeAttribute(String name) {
Assert.notNull(name, "Attribute name must not be null");
Object value = this.attributes.remove(name);
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
}
}
public void removeValue(String name) {
removeAttribute(name);
}
/**
* Clear all of this session's attributes.
*/
public void clearAttributes() {
for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Object> entry = it.next();
String name = entry.getKey();
Object value = entry.getValue();
it.remove();
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
}
}
}
public void invalidate() {
this.invalid = true;
clearAttributes();
}
public boolean isInvalid() {
return this.invalid;
}
public void setNew(boolean value) {
this.isNew = value;
}
public boolean isNew() {
return this.isNew;
}
/**
* Serialize the attributes of this session into an object that can be
* turned into a byte array with standard Java serialization.
*
* @return a representation of this session's serialized state
*/
public Serializable serializeState() {
HashMap<String, Serializable> state = new HashMap<String, Serializable>();
for (Iterator<Map.Entry<String, Object>> it = this.attributes.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Object> entry = it.next();
String name = entry.getKey();
Object value = entry.getValue();
it.remove();
if (value instanceof Serializable) {
state.put(name, (Serializable) value);
}
else {
// Not serializable... Servlet containers usually automatically
// unbind the attribute in this case.
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueUnbound(new HttpSessionBindingEvent(this, name, value));
}
}
}
return state;
}
/**
* Deserialize the attributes of this session from a state object created by
* {@link #serializeState()}.
*
* @param state a representation of this session's serialized state
*/
@SuppressWarnings("unchecked")
public void deserializeState(Serializable state) {
Assert.isTrue(state instanceof Map, "Serialized state needs to be of type [java.util.Map]");
this.attributes.putAll((Map<String, Object>) state);
}
}

View File

@@ -0,0 +1,191 @@
/*
* Copyright 2002-2009 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.mock.web;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspWriter;
/**
* Mock implementation of the {@link javax.servlet.jsp.JspWriter} class.
*
* <p>Used for testing the web framework; only necessary for testing
* applications when testing custom JSP tags.
*
* @author Juergen Hoeller
* @since 2.5
*/
public class MockJspWriter extends JspWriter {
private final HttpServletResponse response;
private PrintWriter targetWriter;
/**
* Create a MockJspWriter for the given response,
* using the response's default Writer.
* @param response the servlet response to wrap
*/
public MockJspWriter(HttpServletResponse response) {
this(response, null);
}
/**
* Create a MockJspWriter for the given plain Writer.
* @param targetWriter the target Writer to wrap
*/
public MockJspWriter(Writer targetWriter) {
this(null, targetWriter);
}
/**
* Create a MockJspWriter for the given response.
* @param response the servlet response to wrap
* @param targetWriter the target Writer to wrap
*/
public MockJspWriter(HttpServletResponse response, Writer targetWriter) {
super(DEFAULT_BUFFER, true);
this.response = (response != null ? response : new MockHttpServletResponse());
if (targetWriter instanceof PrintWriter) {
this.targetWriter = (PrintWriter) targetWriter;
}
else if (targetWriter != null) {
this.targetWriter = new PrintWriter(targetWriter);
}
}
/**
* Lazily initialize the target Writer.
*/
protected PrintWriter getTargetWriter() throws IOException {
if (this.targetWriter == null) {
this.targetWriter = this.response.getWriter();
}
return this.targetWriter;
}
public void clear() throws IOException {
if (this.response.isCommitted()) {
throw new IOException("Response already committed");
}
this.response.resetBuffer();
}
public void clearBuffer() throws IOException {
}
public void flush() throws IOException {
this.response.flushBuffer();
}
public void close() throws IOException {
flush();
}
public int getRemaining() {
return Integer.MAX_VALUE;
}
public void newLine() throws IOException {
getTargetWriter().println();
}
public void write(char value[], int offset, int length) throws IOException {
getTargetWriter().write(value, offset, length);
}
public void print(boolean value) throws IOException {
getTargetWriter().print(value);
}
public void print(char value) throws IOException {
getTargetWriter().print(value);
}
public void print(char[] value) throws IOException {
getTargetWriter().print(value);
}
public void print(double value) throws IOException {
getTargetWriter().print(value);
}
public void print(float value) throws IOException {
getTargetWriter().print(value);
}
public void print(int value) throws IOException {
getTargetWriter().print(value);
}
public void print(long value) throws IOException {
getTargetWriter().print(value);
}
public void print(Object value) throws IOException {
getTargetWriter().print(value);
}
public void print(String value) throws IOException {
getTargetWriter().print(value);
}
public void println() throws IOException {
getTargetWriter().println();
}
public void println(boolean value) throws IOException {
getTargetWriter().println(value);
}
public void println(char value) throws IOException {
getTargetWriter().println(value);
}
public void println(char[] value) throws IOException {
getTargetWriter().println(value);
}
public void println(double value) throws IOException {
getTargetWriter().println(value);
}
public void println(float value) throws IOException {
getTargetWriter().println(value);
}
public void println(int value) throws IOException {
getTargetWriter().println(value);
}
public void println(long value) throws IOException {
getTargetWriter().println(value);
}
public void println(Object value) throws IOException {
getTargetWriter().println(value);
}
public void println(String value) throws IOException {
getTargetWriter().println(value);
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* Mock implementation of the {@link org.springframework.web.multipart.MultipartFile}
* interface.
*
* <p>Useful in conjunction with a {@link MockMultipartHttpServletRequest}
* for testing application controllers that access multipart uploads.
*
* @author Juergen Hoeller
* @author Eric Crampton
* @since 2.0
* @see MockMultipartHttpServletRequest
*/
public class MockMultipartFile implements MultipartFile {
private final String name;
private String originalFilename;
private String contentType;
private final byte[] content;
/**
* Create a new MockMultipartFile with the given content.
* @param name the name of the file
* @param content the content of the file
*/
public MockMultipartFile(String name, byte[] content) {
this(name, "", null, content);
}
/**
* Create a new MockMultipartFile with the given content.
* @param name the name of the file
* @param contentStream the content of the file as stream
* @throws IOException if reading from the stream failed
*/
public MockMultipartFile(String name, InputStream contentStream) throws IOException {
this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
}
/**
* Create a new MockMultipartFile with the given content.
* @param name the name of the file
* @param originalFilename the original filename (as on the client's machine)
* @param contentType the content type (if known)
* @param content the content of the file
*/
public MockMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
Assert.hasLength(name, "Name must not be null");
this.name = name;
this.originalFilename = (originalFilename != null ? originalFilename : "");
this.contentType = contentType;
this.content = (content != null ? content : new byte[0]);
}
/**
* Create a new MockMultipartFile with the given content.
* @param name the name of the file
* @param originalFilename the original filename (as on the client's machine)
* @param contentType the content type (if known)
* @param contentStream the content of the file as stream
* @throws IOException if reading from the stream failed
*/
public MockMultipartFile(String name, String originalFilename, String contentType, InputStream contentStream)
throws IOException {
this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
}
public String getName() {
return this.name;
}
public String getOriginalFilename() {
return this.originalFilename;
}
public String getContentType() {
return this.contentType;
}
public boolean isEmpty() {
return (this.content.length == 0);
}
public long getSize() {
return this.content.length;
}
public byte[] getBytes() throws IOException {
return this.content;
}
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.content);
}
public void transferTo(File dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.content, dest);
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
/**
* Mock implementation of the
* {@link org.springframework.web.multipart.MultipartHttpServletRequest} interface.
*
* <p>Useful for testing application controllers that access multipart uploads.
* The {@link MockMultipartFile} can be used to populate these mock requests
* with files.
*
* @author Juergen Hoeller
* @author Eric Crampton
* @author Arjen Poutsma
* @since 2.0
* @see MockMultipartFile
*/
public class MockMultipartHttpServletRequest extends MockHttpServletRequest implements MultipartHttpServletRequest {
private final MultiValueMap<String, MultipartFile> multipartFiles =
new LinkedMultiValueMap<String, MultipartFile>();
public MockMultipartHttpServletRequest() {
setMethod("POST");
setContentType("multipart/form-data");
}
/**
* Add a file to this request. The parameter name from the multipart
* form is taken from the {@link MultipartFile#getName()}.
* @param file multipart file to be added
*/
public void addFile(MultipartFile file) {
Assert.notNull(file, "MultipartFile must not be null");
this.multipartFiles.add(file.getName(), file);
}
public Iterator<String> getFileNames() {
return this.multipartFiles.keySet().iterator();
}
public MultipartFile getFile(String name) {
return this.multipartFiles.getFirst(name);
}
public List<MultipartFile> getFiles(String name) {
List<MultipartFile> multipartFiles = this.multipartFiles.get(name);
if (multipartFiles != null) {
return multipartFiles;
}
else {
return Collections.emptyList();
}
}
public Map<String, MultipartFile> getFileMap() {
return this.multipartFiles.toSingleValueMap();
}
public MultiValueMap<String, MultipartFile> getMultiFileMap() {
return new LinkedMultiValueMap<String, MultipartFile>(this.multipartFiles);
}
public String getMultipartContentType(String paramOrFileName) {
MultipartFile file = getFile(paramOrFileName);
if (file != null) {
return file.getContentType();
}
else {
return null;
}
}
public HttpMethod getRequestMethod() {
return HttpMethod.valueOf(getMethod());
}
public HttpHeaders getRequestHeaders() {
HttpHeaders headers = new HttpHeaders();
Enumeration<String> headerNames = getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, Collections.list(getHeaders(headerName)));
}
return headers;
}
public HttpHeaders getMultipartHeaders(String paramOrFileName) {
String contentType = getMultipartContentType(paramOrFileName);
if (contentType != null) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", contentType);
return headers;
}
else {
return null;
}
}
}

View File

@@ -0,0 +1,352 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.el.ELContext;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.el.ExpressionEvaluator;
import javax.servlet.jsp.el.VariableResolver;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.servlet.jsp.PageContext} interface.
*
* <p>Used for testing the web framework; only necessary for testing
* applications when testing custom JSP tags.
*
* <p>Note: Expects initialization via the constructor rather than via the
* <code>PageContext.initialize</code> method. Does not support writing to
* a JspWriter, request dispatching, and <code>handlePageException</code> calls.
*
* @author Juergen Hoeller
* @since 1.0.2
*/
@SuppressWarnings("deprecation")
public class MockPageContext extends PageContext {
private final ServletContext servletContext;
private final HttpServletRequest request;
private final HttpServletResponse response;
private final ServletConfig servletConfig;
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private JspWriter out;
/**
* Create new MockPageContext with a default {@link MockServletContext},
* {@link MockHttpServletRequest}, {@link MockHttpServletResponse},
* {@link MockServletConfig}.
*/
public MockPageContext() {
this(null, null, null, null);
}
/**
* Create new MockPageContext with a default {@link MockHttpServletRequest},
* {@link MockHttpServletResponse}, {@link MockServletConfig}.
* @param servletContext the ServletContext that the JSP page runs in
* (only necessary when actually accessing the ServletContext)
*/
public MockPageContext(ServletContext servletContext) {
this(servletContext, null, null, null);
}
/**
* Create new MockPageContext with a MockHttpServletResponse,
* MockServletConfig.
* @param servletContext the ServletContext that the JSP page runs in
* @param request the current HttpServletRequest
* (only necessary when actually accessing the request)
*/
public MockPageContext(ServletContext servletContext, HttpServletRequest request) {
this(servletContext, request, null, null);
}
/**
* Create new MockPageContext with a MockServletConfig.
* @param servletContext the ServletContext that the JSP page runs in
* @param request the current HttpServletRequest
* @param response the current HttpServletResponse
* (only necessary when actually writing to the response)
*/
public MockPageContext(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) {
this(servletContext, request, response, null);
}
/**
* Create new MockServletConfig.
* @param servletContext the ServletContext that the JSP page runs in
* @param request the current HttpServletRequest
* @param response the current HttpServletResponse
* @param servletConfig the ServletConfig (hardly ever accessed from within a tag)
*/
public MockPageContext(ServletContext servletContext, HttpServletRequest request,
HttpServletResponse response, ServletConfig servletConfig) {
this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
this.request = (request != null ? request : new MockHttpServletRequest(servletContext));
this.response = (response != null ? response : new MockHttpServletResponse());
this.servletConfig = (servletConfig != null ? servletConfig : new MockServletConfig(servletContext));
}
public void initialize(
Servlet servlet, ServletRequest request, ServletResponse response,
String errorPageURL, boolean needsSession, int bufferSize, boolean autoFlush) {
throw new UnsupportedOperationException("Use appropriate constructor");
}
public void release() {
}
public void setAttribute(String name, Object value) {
Assert.notNull(name, "Attribute name must not be null");
if (value != null) {
this.attributes.put(name, value);
}
else {
this.attributes.remove(name);
}
}
public void setAttribute(String name, Object value, int scope) {
Assert.notNull(name, "Attribute name must not be null");
switch (scope) {
case PAGE_SCOPE:
setAttribute(name, value);
break;
case REQUEST_SCOPE:
this.request.setAttribute(name, value);
break;
case SESSION_SCOPE:
this.request.getSession().setAttribute(name, value);
break;
case APPLICATION_SCOPE:
this.servletContext.setAttribute(name, value);
break;
default:
throw new IllegalArgumentException("Invalid scope: " + scope);
}
}
public Object getAttribute(String name) {
Assert.notNull(name, "Attribute name must not be null");
return this.attributes.get(name);
}
public Object getAttribute(String name, int scope) {
Assert.notNull(name, "Attribute name must not be null");
switch (scope) {
case PAGE_SCOPE:
return getAttribute(name);
case REQUEST_SCOPE:
return this.request.getAttribute(name);
case SESSION_SCOPE:
HttpSession session = this.request.getSession(false);
return (session != null ? session.getAttribute(name) : null);
case APPLICATION_SCOPE:
return this.servletContext.getAttribute(name);
default:
throw new IllegalArgumentException("Invalid scope: " + scope);
}
}
public Object findAttribute(String name) {
Object value = getAttribute(name);
if (value == null) {
value = getAttribute(name, REQUEST_SCOPE);
if (value == null) {
value = getAttribute(name, SESSION_SCOPE);
if (value == null) {
value = getAttribute(name, APPLICATION_SCOPE);
}
}
}
return value;
}
public void removeAttribute(String name) {
Assert.notNull(name, "Attribute name must not be null");
this.removeAttribute(name, PageContext.PAGE_SCOPE);
this.removeAttribute(name, PageContext.REQUEST_SCOPE);
this.removeAttribute(name, PageContext.SESSION_SCOPE);
this.removeAttribute(name, PageContext.APPLICATION_SCOPE);
}
public void removeAttribute(String name, int scope) {
Assert.notNull(name, "Attribute name must not be null");
switch (scope) {
case PAGE_SCOPE:
this.attributes.remove(name);
break;
case REQUEST_SCOPE:
this.request.removeAttribute(name);
break;
case SESSION_SCOPE:
this.request.getSession().removeAttribute(name);
break;
case APPLICATION_SCOPE:
this.servletContext.removeAttribute(name);
break;
default:
throw new IllegalArgumentException("Invalid scope: " + scope);
}
}
public int getAttributesScope(String name) {
if (getAttribute(name) != null) {
return PAGE_SCOPE;
}
else if (getAttribute(name, REQUEST_SCOPE) != null) {
return REQUEST_SCOPE;
}
else if (getAttribute(name, SESSION_SCOPE) != null) {
return SESSION_SCOPE;
}
else if (getAttribute(name, APPLICATION_SCOPE) != null) {
return APPLICATION_SCOPE;
}
else {
return 0;
}
}
public Enumeration<String> getAttributeNames() {
return Collections.enumeration(this.attributes.keySet());
}
@SuppressWarnings("unchecked")
public Enumeration<String> getAttributeNamesInScope(int scope) {
switch (scope) {
case PAGE_SCOPE:
return getAttributeNames();
case REQUEST_SCOPE:
return this.request.getAttributeNames();
case SESSION_SCOPE:
HttpSession session = this.request.getSession(false);
return (session != null ? session.getAttributeNames() : null);
case APPLICATION_SCOPE:
return this.servletContext.getAttributeNames();
default:
throw new IllegalArgumentException("Invalid scope: " + scope);
}
}
public JspWriter getOut() {
if (this.out == null) {
this.out = new MockJspWriter(this.response);
}
return this.out;
}
public ExpressionEvaluator getExpressionEvaluator() {
return new MockExpressionEvaluator(this);
}
public ELContext getELContext() {
return null;
}
public VariableResolver getVariableResolver() {
return null;
}
public HttpSession getSession() {
return this.request.getSession();
}
public Object getPage() {
return this;
}
public ServletRequest getRequest() {
return this.request;
}
public ServletResponse getResponse() {
return this.response;
}
public Exception getException() {
return null;
}
public ServletConfig getServletConfig() {
return this.servletConfig;
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void forward(String path) throws ServletException, IOException {
this.request.getRequestDispatcher(path).forward(this.request, this.response);
}
public void include(String path) throws ServletException, IOException {
this.request.getRequestDispatcher(path).include(this.request, this.response);
}
public void include(String path, boolean flush) throws ServletException, IOException {
this.request.getRequestDispatcher(path).include(this.request, this.response);
if (flush) {
this.response.flushBuffer();
}
}
public byte[] getContentAsByteArray() {
Assert.isTrue(this.response instanceof MockHttpServletResponse);
return ((MockHttpServletResponse) this.response).getContentAsByteArray();
}
public String getContentAsString() throws UnsupportedEncodingException {
Assert.isTrue(this.response instanceof MockHttpServletResponse);
return ((MockHttpServletResponse) this.response).getContentAsString();
}
public void handlePageException(Exception ex) throws ServletException, IOException {
throw new ServletException("Page exception", ex);
}
public void handlePageException(Throwable ex) throws ServletException, IOException {
throw new ServletException("Page exception", ex);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2002-2010 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.mock.web;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.servlet.RequestDispatcher} interface.
*
* <p>Used for testing the web framework; typically not necessary for
* testing application controllers.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 1.0.2
*/
public class MockRequestDispatcher implements RequestDispatcher {
private final Log logger = LogFactory.getLog(getClass());
private final String url;
/**
* Create a new MockRequestDispatcher for the given URL.
* @param url the URL to dispatch to.
*/
public MockRequestDispatcher(String url) {
Assert.notNull(url, "URL must not be null");
this.url = url;
}
public void forward(ServletRequest request, ServletResponse response) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
if (response.isCommitted()) {
throw new IllegalStateException("Cannot perform forward - response is already committed");
}
getMockHttpServletResponse(response).setForwardedUrl(this.url);
if (logger.isDebugEnabled()) {
logger.debug("MockRequestDispatcher: forwarding to URL [" + this.url + "]");
}
}
public void include(ServletRequest request, ServletResponse response) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
getMockHttpServletResponse(response).addIncludedUrl(this.url);
if (logger.isDebugEnabled()) {
logger.debug("MockRequestDispatcher: including URL [" + this.url + "]");
}
}
/**
* Obtain the underlying MockHttpServletResponse,
* unwrapping {@link HttpServletResponseWrapper} decorators if necessary.
*/
protected MockHttpServletResponse getMockHttpServletResponse(ServletResponse response) {
if (response instanceof MockHttpServletResponse) {
return (MockHttpServletResponse) response;
}
if (response instanceof HttpServletResponseWrapper) {
return getMockHttpServletResponse(((HttpServletResponseWrapper) response).getResponse());
}
throw new IllegalArgumentException("MockRequestDispatcher requires MockHttpServletResponse");
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2009 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.mock.web;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.servlet.ServletConfig} interface.
*
* <p>Used for testing the web framework; typically not necessary for
* testing application controllers.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 1.0.2
*/
public class MockServletConfig implements ServletConfig {
private final ServletContext servletContext;
private final String servletName;
private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
/**
* Create a new MockServletConfig with a default {@link MockServletContext}.
*/
public MockServletConfig() {
this(null, "");
}
/**
* Create a new MockServletConfig with a default {@link MockServletContext}.
* @param servletName the name of the servlet
*/
public MockServletConfig(String servletName) {
this(null, servletName);
}
/**
* Create a new MockServletConfig.
* @param servletContext the ServletContext that the servlet runs in
*/
public MockServletConfig(ServletContext servletContext) {
this(servletContext, "");
}
/**
* Create a new MockServletConfig.
* @param servletContext the ServletContext that the servlet runs in
* @param servletName the name of the servlet
*/
public MockServletConfig(ServletContext servletContext, String servletName) {
this.servletContext = (servletContext != null ? servletContext : new MockServletContext());
this.servletName = servletName;
}
public String getServletName() {
return this.servletName;
}
public ServletContext getServletContext() {
return this.servletContext;
}
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
this.initParameters.put(name, value);
}
public String getInitParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
return this.initParameters.get(name);
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
}

View File

@@ -0,0 +1,423 @@
/*
* Copyright 2002-2011 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.mock.web;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.activation.FileTypeMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.util.WebUtils;
/**
* Mock implementation of the {@link javax.servlet.ServletContext} interface.
*
* <p>Compatible with Servlet 2.5 and partially with Servlet 3.0. Can be configured to
* expose a specific version through {@link #setMajorVersion}/{@link #setMinorVersion};
* default is 2.5. Note that Servlet 3.0 support is limited: servlet, filter and listener
* registration methods are not supported; neither is cookie or JSP configuration.
* We generally do not recommend to unit-test your ServletContainerInitializers and
* WebApplicationInitializers which is where those registration methods would be used.
*
* <p>Used for testing the Spring web framework; only rarely necessary for testing
* application controllers. As long as application components don't explicitly
* access the ServletContext, ClassPathXmlApplicationContext or
* FileSystemXmlApplicationContext can be used to load the context files for testing,
* even for DispatcherServlet context definitions.
*
* <p>For setting up a full WebApplicationContext in a test environment, you can
* use XmlWebApplicationContext (or GenericWebApplicationContext), passing in an
* appropriate MockServletContext instance. You might want to configure your
* MockServletContext with a FileSystemResourceLoader in that case, to make your
* resource paths interpreted as relative file system locations.
*
* <p>A common setup is to point your JVM working directory to the root of your
* web application directory, in combination with filesystem-based resource loading.
* This allows to load the context files as used in the web application, with
* relative paths getting interpreted correctly. Such a setup will work with both
* FileSystemXmlApplicationContext (which will load straight from the file system)
* and XmlWebApplicationContext with an underlying MockServletContext (as long as
* the MockServletContext has been configured with a FileSystemResourceLoader).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 1.0.2
* @see #MockServletContext(org.springframework.core.io.ResourceLoader)
* @see org.springframework.web.context.support.XmlWebApplicationContext
* @see org.springframework.web.context.support.GenericWebApplicationContext
* @see org.springframework.context.support.ClassPathXmlApplicationContext
* @see org.springframework.context.support.FileSystemXmlApplicationContext
*/
public class MockServletContext implements ServletContext {
private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir";
private final Log logger = LogFactory.getLog(getClass());
private final ResourceLoader resourceLoader;
private final String resourceBasePath;
private String contextPath = "";
private int majorVersion = 2;
private int minorVersion = 5;
private int effectiveMajorVersion = 2;
private int effectiveMinorVersion = 5;
private final Map<String, ServletContext> contexts = new HashMap<String, ServletContext>();
private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private String servletContextName = "MockServletContext";
private final Set<String> declaredRoles = new HashSet<String>();
/**
* Create a new MockServletContext, using no base path and a
* DefaultResourceLoader (i.e. the classpath root as WAR root).
* @see org.springframework.core.io.DefaultResourceLoader
*/
public MockServletContext() {
this("", null);
}
/**
* Create a new MockServletContext, using a DefaultResourceLoader.
* @param resourceBasePath the WAR root directory (should not end with a slash)
* @see org.springframework.core.io.DefaultResourceLoader
*/
public MockServletContext(String resourceBasePath) {
this(resourceBasePath, null);
}
/**
* Create a new MockServletContext, using the specified ResourceLoader
* and no base path.
* @param resourceLoader the ResourceLoader to use (or null for the default)
*/
public MockServletContext(ResourceLoader resourceLoader) {
this("", resourceLoader);
}
/**
* Create a new MockServletContext.
* @param resourceBasePath the WAR root directory (should not end with a slash)
* @param resourceLoader the ResourceLoader to use (or null for the default)
*/
public MockServletContext(String resourceBasePath, ResourceLoader resourceLoader) {
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
this.resourceBasePath = (resourceBasePath != null ? resourceBasePath : "");
// Use JVM temp dir as ServletContext temp dir.
String tempDir = System.getProperty(TEMP_DIR_SYSTEM_PROPERTY);
if (tempDir != null) {
this.attributes.put(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE, new File(tempDir));
}
}
/**
* Build a full resource location for the given path,
* prepending the resource base path of this MockServletContext.
* @param path the path as specified
* @return the full resource path
*/
protected String getResourceLocation(String path) {
if (!path.startsWith("/")) {
path = "/" + path;
}
return this.resourceBasePath + path;
}
public void setContextPath(String contextPath) {
this.contextPath = (contextPath != null ? contextPath : "");
}
/* This is a Servlet API 2.5 method. */
public String getContextPath() {
return this.contextPath;
}
public void registerContext(String contextPath, ServletContext context) {
this.contexts.put(contextPath, context);
}
public ServletContext getContext(String contextPath) {
if (this.contextPath.equals(contextPath)) {
return this;
}
return this.contexts.get(contextPath);
}
public void setMajorVersion(int majorVersion) {
this.majorVersion = majorVersion;
}
public int getMajorVersion() {
return this.majorVersion;
}
public void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
}
public int getMinorVersion() {
return this.minorVersion;
}
public void setEffectiveMajorVersion(int effectiveMajorVersion) {
this.effectiveMajorVersion = effectiveMajorVersion;
}
public int getEffectiveMajorVersion() {
return this.effectiveMajorVersion;
}
public void setEffectiveMinorVersion(int effectiveMinorVersion) {
this.effectiveMinorVersion = effectiveMinorVersion;
}
public int getEffectiveMinorVersion() {
return this.effectiveMinorVersion;
}
public String getMimeType(String filePath) {
return MimeTypeResolver.getMimeType(filePath);
}
public Set<String> getResourcePaths(String path) {
String actualPath = (path.endsWith("/") ? path : path + "/");
Resource resource = this.resourceLoader.getResource(getResourceLocation(actualPath));
try {
File file = resource.getFile();
String[] fileList = file.list();
if (ObjectUtils.isEmpty(fileList)) {
return null;
}
Set<String> resourcePaths = new LinkedHashSet<String>(fileList.length);
for (String fileEntry : fileList) {
String resultPath = actualPath + fileEntry;
if (resource.createRelative(fileEntry).getFile().isDirectory()) {
resultPath += "/";
}
resourcePaths.add(resultPath);
}
return resourcePaths;
}
catch (IOException ex) {
logger.warn("Couldn't get resource paths for " + resource, ex);
return null;
}
}
public URL getResource(String path) throws MalformedURLException {
Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
if (!resource.exists()) {
return null;
}
try {
return resource.getURL();
}
catch (MalformedURLException ex) {
throw ex;
}
catch (IOException ex) {
logger.warn("Couldn't get URL for " + resource, ex);
return null;
}
}
public InputStream getResourceAsStream(String path) {
Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
if (!resource.exists()) {
return null;
}
try {
return resource.getInputStream();
}
catch (IOException ex) {
logger.warn("Couldn't open InputStream for " + resource, ex);
return null;
}
}
public RequestDispatcher getRequestDispatcher(String path) {
if (!path.startsWith("/")) {
throw new IllegalArgumentException("RequestDispatcher path at ServletContext level must start with '/'");
}
return new MockRequestDispatcher(path);
}
public RequestDispatcher getNamedDispatcher(String path) {
return null;
}
public Servlet getServlet(String name) {
return null;
}
public Enumeration<Servlet> getServlets() {
return Collections.enumeration(new HashSet<Servlet>());
}
public Enumeration<String> getServletNames() {
return Collections.enumeration(new HashSet<String>());
}
public void log(String message) {
logger.info(message);
}
public void log(Exception ex, String message) {
logger.info(message, ex);
}
public void log(String message, Throwable ex) {
logger.info(message, ex);
}
public String getRealPath(String path) {
Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
try {
return resource.getFile().getAbsolutePath();
}
catch (IOException ex) {
logger.warn("Couldn't determine real path of resource " + resource, ex);
return null;
}
}
public String getServerInfo() {
return "MockServletContext";
}
public String getInitParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
return this.initParameters.get(name);
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
public boolean setInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
if (this.initParameters.containsKey(name)) {
return false;
}
this.initParameters.put(name, value);
return true;
}
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
this.initParameters.put(name, value);
}
public Object getAttribute(String name) {
Assert.notNull(name, "Attribute name must not be null");
return this.attributes.get(name);
}
public Enumeration<String> getAttributeNames() {
return new Vector<String>(this.attributes.keySet()).elements();
}
public void setAttribute(String name, Object value) {
Assert.notNull(name, "Attribute name must not be null");
if (value != null) {
this.attributes.put(name, value);
}
else {
this.attributes.remove(name);
}
}
public void removeAttribute(String name) {
Assert.notNull(name, "Attribute name must not be null");
this.attributes.remove(name);
}
public void setServletContextName(String servletContextName) {
this.servletContextName = servletContextName;
}
public String getServletContextName() {
return this.servletContextName;
}
public ClassLoader getClassLoader() {
return ClassUtils.getDefaultClassLoader();
}
public void declareRoles(String... roleNames) {
Assert.notNull(roleNames, "Role names array must not be null");
for (String roleName : roleNames) {
Assert.hasLength(roleName, "Role name must not be empty");
this.declaredRoles.add(roleName);
}
}
public Set<String> getDeclaredRoles() {
return Collections.unmodifiableSet(this.declaredRoles);
}
/**
* Inner factory class used to just introduce a Java Activation Framework
* dependency when actually asked to resolve a MIME type.
*/
private static class MimeTypeResolver {
public static String getMimeType(String filePath) {
return FileTypeMap.getDefaultFileTypeMap().getContentType(filePath);
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2002-2009 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.mock.web;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.util.Assert;
/**
* Implementation of the {@link javax.servlet.FilterConfig} interface which
* simply passes the call through to a given Filter/FilterChain combination
* (indicating the next Filter in the chain along with the FilterChain that it is
* supposed to work on) or to a given Servlet (indicating the end of the chain).
*
* @author Juergen Hoeller
* @since 2.0.3
* @see javax.servlet.Filter
* @see javax.servlet.Servlet
* @see MockFilterChain
*/
public class PassThroughFilterChain implements FilterChain {
private Filter filter;
private FilterChain nextFilterChain;
private Servlet servlet;
/**
* Create a new PassThroughFilterChain that delegates to the given Filter,
* calling it with the given FilterChain.
* @param filter the Filter to delegate to
* @param nextFilterChain the FilterChain to use for that next Filter
*/
public PassThroughFilterChain(Filter filter, FilterChain nextFilterChain) {
Assert.notNull(filter, "Filter must not be null");
Assert.notNull(nextFilterChain, "'FilterChain must not be null");
this.filter = filter;
this.nextFilterChain = nextFilterChain;
}
/**
* Create a new PassThroughFilterChain that delegates to the given Servlet.
* @param servlet the Servlet to delegate to
*/
public PassThroughFilterChain(Servlet servlet) {
Assert.notNull(servlet, "Servlet must not be null");
this.servlet = servlet;
}
/**
* Pass the call on to the Filter/Servlet.
*/
public void doFilter(ServletRequest request, ServletResponse response) throws ServletException, IOException {
if (this.filter != null) {
this.filter.doFilter(request, response, this.nextFilterChain);
}
else {
this.servlet.service(request, response);
}
}
}

View File

@@ -0,0 +1,15 @@
/**
*
* A comprehensive set of Servlet API 2.5 mock objects,
* targeted at usage with Spring's web MVC framework.
* Useful for testing web contexts and controllers.
*
* <p>More convenient to use than dynamic mock objects
* (<a href="http://www.easymock.org">EasyMock</a>) or
* existing Servlet API mock objects
* (<a href="http://www.mockobjects.com">MockObjects</a>).
*
*/
package org.springframework.mock.web;

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import javax.portlet.ActionRequest;
import javax.portlet.PortalContext;
import javax.portlet.PortletContext;
import javax.portlet.PortletMode;
/**
* Mock implementation of the {@link javax.portlet.ActionRequest} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockActionRequest extends MockClientDataRequest implements ActionRequest {
/**
* Create a new MockActionRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @see org.springframework.mock.web.portlet.MockPortalContext
* @see org.springframework.mock.web.portlet.MockPortletContext
*/
public MockActionRequest() {
super();
}
/**
* Create a new MockActionRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @param actionName the name of the action to trigger
*/
public MockActionRequest(String actionName) {
super();
setParameter(ActionRequest.ACTION_NAME, actionName);
}
/**
* Create a new MockActionRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @param portletMode the mode that the portlet runs in
*/
public MockActionRequest(PortletMode portletMode) {
super();
setPortletMode(portletMode);
}
/**
* Create a new MockActionRequest with a default {@link MockPortalContext}.
* @param portletContext the PortletContext that the request runs in
*/
public MockActionRequest(PortletContext portletContext) {
super(portletContext);
}
/**
* Create a new MockActionRequest.
* @param portalContext the PortalContext that the request runs in
* @param portletContext the PortletContext that the request runs in
*/
public MockActionRequest(PortalContext portalContext, PortletContext portletContext) {
super(portalContext, portletContext);
}
@Override
protected String getLifecyclePhase() {
return ACTION_PHASE;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.IOException;
import java.util.Map;
import javax.portlet.ActionResponse;
import javax.portlet.PortalContext;
import javax.portlet.PortletMode;
import javax.portlet.PortletModeException;
import javax.portlet.WindowState;
import javax.portlet.WindowStateException;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.portlet.ActionResponse} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockActionResponse extends MockStateAwareResponse implements ActionResponse {
private boolean redirectAllowed = true;
private String redirectedUrl;
/**
* Create a new MockActionResponse with a default {@link MockPortalContext}.
* @see MockPortalContext
*/
public MockActionResponse() {
super();
}
/**
* Create a new MockActionResponse.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
*/
public MockActionResponse(PortalContext portalContext) {
super(portalContext);
}
public void setWindowState(WindowState windowState) throws WindowStateException {
if (this.redirectedUrl != null) {
throw new IllegalStateException("Cannot set WindowState after sendRedirect has been called");
}
super.setWindowState(windowState);
this.redirectAllowed = false;
}
public void setPortletMode(PortletMode portletMode) throws PortletModeException {
if (this.redirectedUrl != null) {
throw new IllegalStateException("Cannot set PortletMode after sendRedirect has been called");
}
super.setPortletMode(portletMode);
this.redirectAllowed = false;
}
public void setRenderParameters(Map<String, String[]> parameters) {
if (this.redirectedUrl != null) {
throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
}
super.setRenderParameters(parameters);
this.redirectAllowed = false;
}
public void setRenderParameter(String key, String value) {
if (this.redirectedUrl != null) {
throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
}
super.setRenderParameter(key, value);
this.redirectAllowed = false;
}
public void setRenderParameter(String key, String[] values) {
if (this.redirectedUrl != null) {
throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
}
super.setRenderParameter(key, values);
this.redirectAllowed = false;
}
public void sendRedirect(String location) throws IOException {
if (!this.redirectAllowed) {
throw new IllegalStateException(
"Cannot call sendRedirect after windowState, portletMode, or renderParameters have been set");
}
Assert.notNull(location, "Redirect URL must not be null");
this.redirectedUrl = location;
}
public void sendRedirect(String location, String renderUrlParamName) throws IOException {
sendRedirect(location);
if (renderUrlParamName != null) {
setRenderParameter(renderUrlParamName, location);
}
}
public String getRedirectedUrl() {
return this.redirectedUrl;
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.portlet.BaseURL;
import javax.portlet.PortletSecurityException;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Mock implementation of the {@link javax.portlet.BaseURL} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public abstract class MockBaseURL implements BaseURL {
public static final String URL_TYPE_RENDER = "render";
public static final String URL_TYPE_ACTION = "action";
private static final String ENCODING = "UTF-8";
protected final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
private boolean secure = false;
private final Map<String, String[]> properties = new LinkedHashMap<String, String[]>();
//---------------------------------------------------------------------
// BaseURL methods
//---------------------------------------------------------------------
public void setParameter(String key, String value) {
Assert.notNull(key, "Parameter key must be null");
Assert.notNull(value, "Parameter value must not be null");
this.parameters.put(key, new String[] {value});
}
public void setParameter(String key, String[] values) {
Assert.notNull(key, "Parameter key must be null");
Assert.notNull(values, "Parameter values must not be null");
this.parameters.put(key, values);
}
public void setParameters(Map<String, String[]> parameters) {
Assert.notNull(parameters, "Parameters Map must not be null");
this.parameters.clear();
this.parameters.putAll(parameters);
}
public Set<String> getParameterNames() {
return this.parameters.keySet();
}
public String getParameter(String name) {
String[] arr = this.parameters.get(name);
return (arr != null && arr.length > 0 ? arr[0] : null);
}
public String[] getParameterValues(String name) {
return this.parameters.get(name);
}
public Map<String, String[]> getParameterMap() {
return Collections.unmodifiableMap(this.parameters);
}
public void setSecure(boolean secure) throws PortletSecurityException {
this.secure = secure;
}
public boolean isSecure() {
return this.secure;
}
public void write(Writer out) throws IOException {
out.write(toString());
}
public void write(Writer out, boolean escapeXML) throws IOException {
out.write(toString());
}
public void addProperty(String key, String value) {
String[] values = this.properties.get(key);
if (values != null) {
this.properties.put(key, StringUtils.addStringToArray(values, value));
}
else {
this.properties.put(key, new String[] {value});
}
}
public void setProperty(String key, String value) {
this.properties.put(key, new String[] {value});
}
public Map<String, String[]> getProperties() {
return Collections.unmodifiableMap(this.properties);
}
protected String encodeParameter(String name, String value) {
try {
return URLEncoder.encode(name, ENCODING) + "=" + URLEncoder.encode(value, ENCODING);
}
catch (UnsupportedEncodingException ex) {
return null;
}
}
protected String encodeParameter(String name, String[] values) {
try {
StringBuilder sb = new StringBuilder();
for (int i = 0, n = values.length; i < n; i++) {
sb.append(i > 0 ? ";" : "").append(URLEncoder.encode(name, ENCODING)).append("=")
.append(URLEncoder.encode(values[i], ENCODING));
}
return sb.toString();
}
catch (UnsupportedEncodingException ex) {
return null;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import javax.portlet.CacheControl;
/**
* Mock implementation of the {@link javax.portlet.CacheControl} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockCacheControl implements CacheControl {
private int expirationTime = 0;
private boolean publicScope = false;
private String etag;
private boolean useCachedContent = false;
public int getExpirationTime() {
return this.expirationTime;
}
public void setExpirationTime(int time) {
this.expirationTime = time;
}
public boolean isPublicScope() {
return this.publicScope;
}
public void setPublicScope(boolean publicScope) {
this.publicScope = publicScope;
}
public String getETag() {
return this.etag;
}
public void setETag(String token) {
this.etag = token;
}
public boolean useCachedContent() {
return this.useCachedContent;
}
public void setUseCachedContent(boolean useCachedContent) {
this.useCachedContent = useCachedContent;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import javax.portlet.ClientDataRequest;
import javax.portlet.PortalContext;
import javax.portlet.PortletContext;
/**
* Mock implementation of the {@link javax.portlet.ClientDataRequest} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockClientDataRequest extends MockPortletRequest implements ClientDataRequest {
private String characterEncoding;
private byte[] content;
private String contentType;
private String method;
/**
* Create a new MockClientDataRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @see org.springframework.mock.web.portlet.MockPortalContext
* @see org.springframework.mock.web.portlet.MockPortletContext
*/
public MockClientDataRequest() {
super();
}
/**
* Create a new MockClientDataRequest with a default {@link MockPortalContext}.
* @param portletContext the PortletContext that the request runs in
*/
public MockClientDataRequest(PortletContext portletContext) {
super(portletContext);
}
/**
* Create a new MockClientDataRequest.
* @param portalContext the PortalContext that the request runs in
* @param portletContext the PortletContext that the request runs in
*/
public MockClientDataRequest(PortalContext portalContext, PortletContext portletContext) {
super(portalContext, portletContext);
}
public void setContent(byte[] content) {
this.content = content;
}
public InputStream getPortletInputStream() throws IOException {
if (this.content != null) {
return new ByteArrayInputStream(this.content);
}
else {
return null;
}
}
public void setCharacterEncoding(String characterEncoding) {
this.characterEncoding = characterEncoding;
}
public BufferedReader getReader() throws UnsupportedEncodingException {
if (this.content != null) {
InputStream sourceStream = new ByteArrayInputStream(this.content);
Reader sourceReader = (this.characterEncoding != null) ?
new InputStreamReader(sourceStream, this.characterEncoding) : new InputStreamReader(sourceStream);
return new BufferedReader(sourceReader);
}
else {
return null;
}
}
public String getCharacterEncoding() {
return this.characterEncoding;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getContentType() {
return this.contentType;
}
public int getContentLength() {
return (this.content != null ? content.length : -1);
}
public void setMethod(String method) {
this.method = method;
}
public String getMethod() {
return this.method;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.Serializable;
import javax.portlet.Event;
import javax.xml.namespace.QName;
/**
* Mock implementation of the {@link javax.portlet.Event} interface.
*
* @author Juergen Hoeller
* @since 3.0
* @see MockEventRequest
*/
public class MockEvent implements Event {
private final QName name;
private final Serializable value;
/**
* Create a new MockEvent with the given name.
* @param name the name of the event
*/
public MockEvent(QName name) {
this.name = name;
this.value = null;
}
/**
* Create a new MockEvent with the given name and value.
* @param name the name of the event
* @param value the associated payload of the event
*/
public MockEvent(QName name, Serializable value) {
this.name = name;
this.value = value;
}
/**
* Create a new MockEvent with the given name.
* @param name the name of the event
*/
public MockEvent(String name) {
this.name = new QName(name);
this.value = null;
}
/**
* Create a new MockEvent with the given name and value.
* @param name the name of the event
* @param value the associated payload of the event
*/
public MockEvent(String name, Serializable value) {
this.name = new QName(name);
this.value = value;
}
public QName getQName() {
return this.name;
}
public String getName() {
return this.name.getLocalPart();
}
public Serializable getValue() {
return this.value;
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import javax.portlet.Event;
import javax.portlet.EventRequest;
import javax.portlet.PortalContext;
import javax.portlet.PortletContext;
/**
* Mock implementation of the {@link javax.portlet.EventRequest} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockEventRequest extends MockPortletRequest implements EventRequest {
private final Event event;
private String method;
/**
* Create a new MockEventRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @param event the event that this request wraps
* @see MockEvent
*/
public MockEventRequest(Event event) {
super();
this.event = event;
}
/**
* Create a new MockEventRequest with a default {@link MockPortalContext}.
* @param event the event that this request wraps
* @param portletContext the PortletContext that the request runs in
* @see MockEvent
*/
public MockEventRequest(Event event, PortletContext portletContext) {
super(portletContext);
this.event = event;
}
/**
* Create a new MockEventRequest.
* @param event the event that this request wraps
* @param portalContext the PortletContext that the request runs in
* @param portletContext the PortletContext that the request runs in
*/
public MockEventRequest(Event event, PortalContext portalContext, PortletContext portletContext) {
super(portalContext, portletContext);
this.event = event;
}
@Override
protected String getLifecyclePhase() {
return EVENT_PHASE;
}
public Event getEvent() {
return this.event;
}
public void setMethod(String method) {
this.method = method;
}
public String getMethod() {
return this.method;
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
/**
* Mock implementation of the {@link javax.portlet.EventResponse} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockEventResponse extends MockStateAwareResponse implements EventResponse {
public void setRenderParameters(EventRequest request) {
setRenderParameters(request.getParameterMap());
}
}

View File

@@ -0,0 +1,243 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import javax.portlet.CacheControl;
import javax.portlet.MimeResponse;
import javax.portlet.PortalContext;
import javax.portlet.PortletRequest;
import javax.portlet.PortletURL;
import javax.portlet.ResourceURL;
import org.springframework.util.CollectionUtils;
import org.springframework.web.util.WebUtils;
/**
* Mock implementation of the {@link javax.portlet.MimeResponse} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockMimeResponse extends MockPortletResponse implements MimeResponse {
private PortletRequest request;
private String contentType;
private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
private PrintWriter writer;
private Locale locale = Locale.getDefault();
private int bufferSize = 4096;
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final CacheControl cacheControl = new MockCacheControl();
private boolean committed;
private String includedUrl;
private String forwardedUrl;
/**
* Create a new MockMimeResponse with a default {@link MockPortalContext}.
* @see org.springframework.mock.web.portlet.MockPortalContext
*/
public MockMimeResponse() {
super();
}
/**
* Create a new MockMimeResponse.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
*/
public MockMimeResponse(PortalContext portalContext) {
super(portalContext);
}
/**
* Create a new MockMimeResponse.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
* @param request the corresponding render/resource request that this response
* is being generated for
*/
public MockMimeResponse(PortalContext portalContext, PortletRequest request) {
super(portalContext);
this.request = request;
}
//---------------------------------------------------------------------
// RenderResponse methods
//---------------------------------------------------------------------
public void setContentType(String contentType) {
if (this.request != null) {
Enumeration<String> supportedTypes = this.request.getResponseContentTypes();
if (!CollectionUtils.contains(supportedTypes, contentType)) {
throw new IllegalArgumentException("Content type [" + contentType + "] not in supported list: " +
Collections.list(supportedTypes));
}
}
this.contentType = contentType;
}
public String getContentType() {
return this.contentType;
}
public void setCharacterEncoding(String characterEncoding) {
this.characterEncoding = characterEncoding;
}
public String getCharacterEncoding() {
return this.characterEncoding;
}
public PrintWriter getWriter() throws UnsupportedEncodingException {
if (this.writer == null) {
Writer targetWriter = (this.characterEncoding != null
? new OutputStreamWriter(this.outputStream, this.characterEncoding)
: new OutputStreamWriter(this.outputStream));
this.writer = new PrintWriter(targetWriter);
}
return this.writer;
}
public byte[] getContentAsByteArray() {
flushBuffer();
return this.outputStream.toByteArray();
}
public String getContentAsString() throws UnsupportedEncodingException {
flushBuffer();
return (this.characterEncoding != null)
? this.outputStream.toString(this.characterEncoding)
: this.outputStream.toString();
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public Locale getLocale() {
return this.locale;
}
public void setBufferSize(int bufferSize) {
this.bufferSize = bufferSize;
}
public int getBufferSize() {
return this.bufferSize;
}
public void flushBuffer() {
if (this.writer != null) {
this.writer.flush();
}
if (this.outputStream != null) {
try {
this.outputStream.flush();
}
catch (IOException ex) {
throw new IllegalStateException("Could not flush OutputStream: " + ex.getMessage());
}
}
this.committed = true;
}
public void resetBuffer() {
if (this.committed) {
throw new IllegalStateException("Cannot reset buffer - response is already committed");
}
this.outputStream.reset();
}
public void setCommitted(boolean committed) {
this.committed = committed;
}
public boolean isCommitted() {
return this.committed;
}
public void reset() {
resetBuffer();
this.characterEncoding = null;
this.contentType = null;
this.locale = null;
}
public OutputStream getPortletOutputStream() throws IOException {
return this.outputStream;
}
public PortletURL createRenderURL() {
return new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_RENDER);
}
public PortletURL createActionURL() {
return new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_ACTION);
}
public ResourceURL createResourceURL() {
return new MockResourceURL();
}
public CacheControl getCacheControl() {
return this.cacheControl;
}
//---------------------------------------------------------------------
// Methods for MockPortletRequestDispatcher
//---------------------------------------------------------------------
public void setIncludedUrl(String includedUrl) {
this.includedUrl = includedUrl;
}
public String getIncludedUrl() {
return this.includedUrl;
}
public void setForwardedUrl(String forwardedUrl) {
this.forwardedUrl = forwardedUrl;
}
public String getForwardedUrl() {
return this.forwardedUrl;
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2002-2011 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.mock.web.portlet;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.portlet.multipart.MultipartActionRequest;
/**
* Mock implementation of the
* {@link org.springframework.web.portlet.multipart.MultipartActionRequest} interface.
*
* <p>Useful for testing application controllers that access multipart uploads.
* The {@link org.springframework.mock.web.MockMultipartFile} can be used to
* populate these mock requests with files.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.0
* @see org.springframework.mock.web.MockMultipartFile
*/
public class MockMultipartActionRequest extends MockActionRequest implements MultipartActionRequest {
private final MultiValueMap<String, MultipartFile> multipartFiles =
new LinkedMultiValueMap<String, MultipartFile>();
/**
* Add a file to this request. The parameter name from the multipart
* form is taken from the {@link org.springframework.web.multipart.MultipartFile#getName()}.
* @param file multipart file to be added
*/
public void addFile(MultipartFile file) {
Assert.notNull(file, "MultipartFile must not be null");
this.multipartFiles.add(file.getName(), file);
}
public Iterator<String> getFileNames() {
return this.multipartFiles.keySet().iterator();
}
public MultipartFile getFile(String name) {
return this.multipartFiles.getFirst(name);
}
public List<MultipartFile> getFiles(String name) {
List<MultipartFile> multipartFiles = this.multipartFiles.get(name);
if (multipartFiles != null) {
return multipartFiles;
}
else {
return Collections.emptyList();
}
}
public Map<String, MultipartFile> getFileMap() {
return this.multipartFiles.toSingleValueMap();
}
public MultiValueMap<String, MultipartFile> getMultiFileMap() {
return new LinkedMultiValueMap<String, MultipartFile>(this.multipartFiles);
}
public String getMultipartContentType(String paramOrFileName) {
MultipartFile file = getFile(paramOrFileName);
if (file != null) {
return file.getContentType();
}
else {
return null;
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.portlet.PortalContext;
import javax.portlet.PortletMode;
import javax.portlet.WindowState;
/**
* Mock implementation of the {@link javax.portlet.PortalContext} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortalContext implements PortalContext {
private final Map<String, String> properties = new HashMap<String, String>();
private final List<PortletMode> portletModes;
private final List<WindowState> windowStates;
/**
* Create a new MockPortalContext
* with default PortletModes (VIEW, EDIT, HELP)
* and default WindowStates (NORMAL, MAXIMIZED, MINIMIZED).
* @see javax.portlet.PortletMode
* @see javax.portlet.WindowState
*/
public MockPortalContext() {
this.portletModes = new ArrayList<PortletMode>(3);
this.portletModes.add(PortletMode.VIEW);
this.portletModes.add(PortletMode.EDIT);
this.portletModes.add(PortletMode.HELP);
this.windowStates = new ArrayList<WindowState>(3);
this.windowStates.add(WindowState.NORMAL);
this.windowStates.add(WindowState.MAXIMIZED);
this.windowStates.add(WindowState.MINIMIZED);
}
/**
* Create a new MockPortalContext with the given PortletModes and WindowStates.
* @param supportedPortletModes the List of supported PortletMode instances
* @param supportedWindowStates the List of supported WindowState instances
* @see javax.portlet.PortletMode
* @see javax.portlet.WindowState
*/
public MockPortalContext(List<PortletMode> supportedPortletModes, List<WindowState> supportedWindowStates) {
this.portletModes = new ArrayList<PortletMode>(supportedPortletModes);
this.windowStates = new ArrayList<WindowState>(supportedWindowStates);
}
public String getPortalInfo() {
return "MockPortal/1.0";
}
public void setProperty(String name, String value) {
this.properties.put(name, value);
}
public String getProperty(String name) {
return this.properties.get(name);
}
public Enumeration<String> getPropertyNames() {
return Collections.enumeration(this.properties.keySet());
}
public Enumeration<PortletMode> getSupportedPortletModes() {
return Collections.enumeration(this.portletModes);
}
public Enumeration<WindowState> getSupportedWindowStates() {
return Collections.enumeration(this.windowStates);
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.portlet.PortletConfig} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletConfig implements PortletConfig {
private final PortletContext portletContext;
private final String portletName;
private final Map<Locale, ResourceBundle> resourceBundles = new HashMap<Locale, ResourceBundle>();
private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
private final Set<String> publicRenderParameterNames = new LinkedHashSet<String>();
private String defaultNamespace = XMLConstants.NULL_NS_URI;
private final Set<QName> publishingEventQNames = new LinkedHashSet<QName>();
private final Set<QName> processingEventQNames = new LinkedHashSet<QName>();
private final Set<Locale> supportedLocales = new LinkedHashSet<Locale>();
private final Map<String, String[]> containerRuntimeOptions = new LinkedHashMap<String, String[]>();
/**
* Create a new MockPortletConfig with a default {@link MockPortletContext}.
*/
public MockPortletConfig() {
this(null, "");
}
/**
* Create a new MockPortletConfig with a default {@link MockPortletContext}.
* @param portletName the name of the portlet
*/
public MockPortletConfig(String portletName) {
this(null, portletName);
}
/**
* Create a new MockPortletConfig.
* @param portletContext the PortletContext that the portlet runs in
*/
public MockPortletConfig(PortletContext portletContext) {
this(portletContext, "");
}
/**
* Create a new MockPortletConfig.
* @param portletContext the PortletContext that the portlet runs in
* @param portletName the name of the portlet
*/
public MockPortletConfig(PortletContext portletContext, String portletName) {
this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
this.portletName = portletName;
}
public String getPortletName() {
return this.portletName;
}
public PortletContext getPortletContext() {
return this.portletContext;
}
public void setResourceBundle(Locale locale, ResourceBundle resourceBundle) {
Assert.notNull(locale, "Locale must not be null");
this.resourceBundles.put(locale, resourceBundle);
}
public ResourceBundle getResourceBundle(Locale locale) {
Assert.notNull(locale, "Locale must not be null");
return this.resourceBundles.get(locale);
}
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
this.initParameters.put(name, value);
}
public String getInitParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
return this.initParameters.get(name);
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
public void addPublicRenderParameterName(String name) {
this.publicRenderParameterNames.add(name);
}
public Enumeration<String> getPublicRenderParameterNames() {
return Collections.enumeration(this.publicRenderParameterNames);
}
public void setDefaultNamespace(String defaultNamespace) {
this.defaultNamespace = defaultNamespace;
}
public String getDefaultNamespace() {
return this.defaultNamespace;
}
public void addPublishingEventQName(QName name) {
this.publishingEventQNames.add(name);
}
public Enumeration<QName> getPublishingEventQNames() {
return Collections.enumeration(this.publishingEventQNames);
}
public void addProcessingEventQName(QName name) {
this.processingEventQNames.add(name);
}
public Enumeration<QName> getProcessingEventQNames() {
return Collections.enumeration(this.processingEventQNames);
}
public void addSupportedLocale(Locale locale) {
this.supportedLocales.add(locale);
}
public Enumeration<Locale> getSupportedLocales() {
return Collections.enumeration(this.supportedLocales);
}
public void addContainerRuntimeOption(String key, String value) {
this.containerRuntimeOptions.put(key, new String[] {value});
}
public void addContainerRuntimeOption(String key, String[] values) {
this.containerRuntimeOptions.put(key, values);
}
public Map<String, String[]> getContainerRuntimeOptions() {
return Collections.unmodifiableMap(this.containerRuntimeOptions);
}
}

View File

@@ -0,0 +1,279 @@
/*
* Copyright 2002-2010 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.mock.web.portlet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.activation.FileTypeMap;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequestDispatcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.web.util.WebUtils;
/**
* Mock implementation of the {@link javax.portlet.PortletContext} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletContext implements PortletContext {
private static final String TEMP_DIR_SYSTEM_PROPERTY = "java.io.tmpdir";
private final Log logger = LogFactory.getLog(getClass());
private final String resourceBasePath;
private final ResourceLoader resourceLoader;
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private final Map<String, String> initParameters = new LinkedHashMap<String, String>();
private String portletContextName = "MockPortletContext";
private Set<String> containerRuntimeOptions = new LinkedHashSet<String>();
/**
* Create a new MockPortletContext with no base path and a
* DefaultResourceLoader (i.e. the classpath root as WAR root).
* @see org.springframework.core.io.DefaultResourceLoader
*/
public MockPortletContext() {
this("", null);
}
/**
* Create a new MockPortletContext using a DefaultResourceLoader.
* @param resourceBasePath the WAR root directory (should not end with a slash)
* @see org.springframework.core.io.DefaultResourceLoader
*/
public MockPortletContext(String resourceBasePath) {
this(resourceBasePath, null);
}
/**
* Create a new MockPortletContext, using the specified ResourceLoader
* and no base path.
* @param resourceLoader the ResourceLoader to use (or null for the default)
*/
public MockPortletContext(ResourceLoader resourceLoader) {
this("", resourceLoader);
}
/**
* Create a new MockPortletContext.
* @param resourceBasePath the WAR root directory (should not end with a slash)
* @param resourceLoader the ResourceLoader to use (or null for the default)
*/
public MockPortletContext(String resourceBasePath, ResourceLoader resourceLoader) {
this.resourceBasePath = (resourceBasePath != null ? resourceBasePath : "");
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
// Use JVM temp dir as PortletContext temp dir.
String tempDir = System.getProperty(TEMP_DIR_SYSTEM_PROPERTY);
if (tempDir != null) {
this.attributes.put(WebUtils.TEMP_DIR_CONTEXT_ATTRIBUTE, new File(tempDir));
}
}
/**
* Build a full resource location for the given path,
* prepending the resource base path of this MockPortletContext.
* @param path the path as specified
* @return the full resource path
*/
protected String getResourceLocation(String path) {
if (!path.startsWith("/")) {
path = "/" + path;
}
return this.resourceBasePath + path;
}
public String getServerInfo() {
return "MockPortal/1.0";
}
public PortletRequestDispatcher getRequestDispatcher(String path) {
if (!path.startsWith("/")) {
throw new IllegalArgumentException(
"PortletRequestDispatcher path at PortletContext level must start with '/'");
}
return new MockPortletRequestDispatcher(path);
}
public PortletRequestDispatcher getNamedDispatcher(String path) {
return null;
}
public InputStream getResourceAsStream(String path) {
Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
try {
return resource.getInputStream();
}
catch (IOException ex) {
logger.info("Couldn't open InputStream for " + resource, ex);
return null;
}
}
public int getMajorVersion() {
return 2;
}
public int getMinorVersion() {
return 0;
}
public String getMimeType(String filePath) {
return MimeTypeResolver.getMimeType(filePath);
}
public String getRealPath(String path) {
Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
try {
return resource.getFile().getAbsolutePath();
}
catch (IOException ex) {
logger.info("Couldn't determine real path of resource " + resource, ex);
return null;
}
}
public Set<String> getResourcePaths(String path) {
Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
try {
File file = resource.getFile();
String[] fileList = file.list();
String prefix = (path.endsWith("/") ? path : path + "/");
Set<String> resourcePaths = new HashSet<String>(fileList.length);
for (String fileEntry : fileList) {
resourcePaths.add(prefix + fileEntry);
}
return resourcePaths;
}
catch (IOException ex) {
logger.info("Couldn't get resource paths for " + resource, ex);
return null;
}
}
public URL getResource(String path) throws MalformedURLException {
Resource resource = this.resourceLoader.getResource(getResourceLocation(path));
try {
return resource.getURL();
}
catch (IOException ex) {
logger.info("Couldn't get URL for " + resource, ex);
return null;
}
}
public Object getAttribute(String name) {
return this.attributes.get(name);
}
public Enumeration<String> getAttributeNames() {
return new Vector<String>(this.attributes.keySet()).elements();
}
public void setAttribute(String name, Object value) {
if (value != null) {
this.attributes.put(name, value);
}
else {
this.attributes.remove(name);
}
}
public void removeAttribute(String name) {
this.attributes.remove(name);
}
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
this.initParameters.put(name, value);
}
public String getInitParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
return this.initParameters.get(name);
}
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
public void log(String message) {
logger.info(message);
}
public void log(String message, Throwable t) {
logger.info(message, t);
}
public void setPortletContextName(String portletContextName) {
this.portletContextName = portletContextName;
}
public String getPortletContextName() {
return this.portletContextName;
}
public void addContainerRuntimeOption(String key) {
this.containerRuntimeOptions.add(key);
}
public Enumeration<String> getContainerRuntimeOptions() {
return Collections.enumeration(this.containerRuntimeOptions);
}
/**
* Inner factory class used to just introduce a Java Activation Framework
* dependency when actually asked to resolve a MIME type.
*/
private static class MimeTypeResolver {
public static String getMimeType(String filePath) {
return FileTypeMap.getDefaultFileTypeMap().getContentType(filePath);
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.portlet.PortletPreferences;
import javax.portlet.PreferencesValidator;
import javax.portlet.ReadOnlyException;
import javax.portlet.ValidatorException;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.portlet.PortletPreferences} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletPreferences implements PortletPreferences {
private PreferencesValidator preferencesValidator;
private final Map<String, String[]> preferences = new LinkedHashMap<String, String[]>();
private final Set<String> readOnly = new HashSet<String>();
public void setReadOnly(String key, boolean readOnly) {
Assert.notNull(key, "Key must not be null");
if (readOnly) {
this.readOnly.add(key);
}
else {
this.readOnly.remove(key);
}
}
public boolean isReadOnly(String key) {
Assert.notNull(key, "Key must not be null");
return this.readOnly.contains(key);
}
public String getValue(String key, String def) {
Assert.notNull(key, "Key must not be null");
String[] values = this.preferences.get(key);
return (values != null && values.length > 0 ? values[0] : def);
}
public String[] getValues(String key, String[] def) {
Assert.notNull(key, "Key must not be null");
String[] values = this.preferences.get(key);
return (values != null && values.length > 0 ? values : def);
}
public void setValue(String key, String value) throws ReadOnlyException {
setValues(key, new String[] {value});
}
public void setValues(String key, String[] values) throws ReadOnlyException {
Assert.notNull(key, "Key must not be null");
if (isReadOnly(key)) {
throw new ReadOnlyException("Preference '" + key + "' is read-only");
}
this.preferences.put(key, values);
}
public Enumeration<String> getNames() {
return Collections.enumeration(this.preferences.keySet());
}
public Map<String, String[]> getMap() {
return Collections.unmodifiableMap(this.preferences);
}
public void reset(String key) throws ReadOnlyException {
Assert.notNull(key, "Key must not be null");
if (isReadOnly(key)) {
throw new ReadOnlyException("Preference '" + key + "' is read-only");
}
this.preferences.remove(key);
}
public void setPreferencesValidator(PreferencesValidator preferencesValidator) {
this.preferencesValidator = preferencesValidator;
}
public void store() throws IOException, ValidatorException {
if (this.preferencesValidator != null) {
this.preferencesValidator.validate(this);
}
}
}

View File

@@ -0,0 +1,532 @@
/*
* Copyright 2002-2010 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.mock.web.portlet;
import java.security.Principal;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.portlet.PortalContext;
import javax.portlet.PortletContext;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.portlet.PortletSession;
import javax.portlet.WindowState;
import javax.servlet.http.Cookie;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Mock implementation of the {@link javax.portlet.PortletRequest} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletRequest implements PortletRequest {
private boolean active = true;
private final PortalContext portalContext;
private final PortletContext portletContext;
private PortletSession session;
private WindowState windowState = WindowState.NORMAL;
private PortletMode portletMode = PortletMode.VIEW;
private PortletPreferences portletPreferences = new MockPortletPreferences();
private final Map<String, List<String>> properties = new LinkedHashMap<String, List<String>>();
private final Map<String, Object> attributes = new LinkedHashMap<String, Object>();
private final Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
private String authType = null;
private String contextPath = "";
private String remoteUser = null;
private Principal userPrincipal = null;
private final Set<String> userRoles = new HashSet<String>();
private boolean secure = false;
private boolean requestedSessionIdValid = true;
private final List<String> responseContentTypes = new LinkedList<String>();
private final List<Locale> locales = new LinkedList<Locale>();
private String scheme = "http";
private String serverName = "localhost";
private int serverPort = 80;
private String windowID;
private Cookie[] cookies;
private final Set<String> publicParameterNames = new HashSet<String>();
/**
* Create a new MockPortletRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
*
* @see MockPortalContext
* @see MockPortletContext
*/
public MockPortletRequest() {
this(null, null);
}
/**
* Create a new MockPortletRequest with a default {@link MockPortalContext}.
*
* @param portletContext the PortletContext that the request runs in
* @see MockPortalContext
*/
public MockPortletRequest(PortletContext portletContext) {
this(null, portletContext);
}
/**
* Create a new MockPortletRequest.
*
* @param portalContext the PortalContext that the request runs in
* @param portletContext the PortletContext that the request runs in
*/
public MockPortletRequest(PortalContext portalContext, PortletContext portletContext) {
this.portalContext = (portalContext != null ? portalContext : new MockPortalContext());
this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
this.responseContentTypes.add("text/html");
this.locales.add(Locale.ENGLISH);
this.attributes.put(LIFECYCLE_PHASE, getLifecyclePhase());
}
// ---------------------------------------------------------------------
// Lifecycle methods
// ---------------------------------------------------------------------
/**
* Return the Portlet 2.0 lifecycle id for the current phase.
*/
protected String getLifecyclePhase() {
return null;
}
/**
* Return whether this request is still active (that is, not completed yet).
*/
public boolean isActive() {
return this.active;
}
/**
* Mark this request as completed.
*/
public void close() {
this.active = false;
}
/**
* Check whether this request is still active (that is, not completed yet),
* throwing an IllegalStateException if not active anymore.
*/
protected void checkActive() throws IllegalStateException {
if (!this.active) {
throw new IllegalStateException("Request is not active anymore");
}
}
// ---------------------------------------------------------------------
// PortletRequest methods
// ---------------------------------------------------------------------
public boolean isWindowStateAllowed(WindowState windowState) {
return CollectionUtils.contains(this.portalContext.getSupportedWindowStates(), windowState);
}
public boolean isPortletModeAllowed(PortletMode portletMode) {
return CollectionUtils.contains(this.portalContext.getSupportedPortletModes(), portletMode);
}
public void setPortletMode(PortletMode portletMode) {
Assert.notNull(portletMode, "PortletMode must not be null");
this.portletMode = portletMode;
}
public PortletMode getPortletMode() {
return this.portletMode;
}
public void setWindowState(WindowState windowState) {
Assert.notNull(windowState, "WindowState must not be null");
this.windowState = windowState;
}
public WindowState getWindowState() {
return this.windowState;
}
public void setPreferences(PortletPreferences preferences) {
Assert.notNull(preferences, "PortletPreferences must not be null");
this.portletPreferences = preferences;
}
public PortletPreferences getPreferences() {
return this.portletPreferences;
}
public void setSession(PortletSession session) {
this.session = session;
if (session instanceof MockPortletSession) {
MockPortletSession mockSession = ((MockPortletSession) session);
mockSession.access();
}
}
public PortletSession getPortletSession() {
return getPortletSession(true);
}
public PortletSession getPortletSession(boolean create) {
checkActive();
// Reset session if invalidated.
if (this.session instanceof MockPortletSession && ((MockPortletSession) this.session).isInvalid()) {
this.session = null;
}
// Create new session if necessary.
if (this.session == null && create) {
this.session = new MockPortletSession(this.portletContext);
}
return this.session;
}
/**
* Set a single value for the specified property.
* <p>
* If there are already one or more values registered for the given property
* key, they will be replaced.
*/
public void setProperty(String key, String value) {
Assert.notNull(key, "Property key must not be null");
List<String> list = new LinkedList<String>();
list.add(value);
this.properties.put(key, list);
}
/**
* Add a single value for the specified property.
* <p>
* If there are already one or more values registered for the given property
* key, the given value will be added to the end of the list.
*/
public void addProperty(String key, String value) {
Assert.notNull(key, "Property key must not be null");
List<String> oldList = this.properties.get(key);
if (oldList != null) {
oldList.add(value);
}
else {
List<String> list = new LinkedList<String>();
list.add(value);
this.properties.put(key, list);
}
}
public String getProperty(String key) {
Assert.notNull(key, "Property key must not be null");
List<String> list = this.properties.get(key);
return (list != null && list.size() > 0 ? (String) list.get(0) : null);
}
public Enumeration<String> getProperties(String key) {
Assert.notNull(key, "property key must not be null");
return Collections.enumeration(this.properties.get(key));
}
public Enumeration<String> getPropertyNames() {
return Collections.enumeration(this.properties.keySet());
}
public PortalContext getPortalContext() {
return this.portalContext;
}
public void setAuthType(String authType) {
this.authType = authType;
}
public String getAuthType() {
return this.authType;
}
public void setContextPath(String contextPath) {
this.contextPath = contextPath;
}
public String getContextPath() {
return this.contextPath;
}
public void setRemoteUser(String remoteUser) {
this.remoteUser = remoteUser;
}
public String getRemoteUser() {
return this.remoteUser;
}
public void setUserPrincipal(Principal userPrincipal) {
this.userPrincipal = userPrincipal;
}
public Principal getUserPrincipal() {
return this.userPrincipal;
}
public void addUserRole(String role) {
this.userRoles.add(role);
}
public boolean isUserInRole(String role) {
return this.userRoles.contains(role);
}
public Object getAttribute(String name) {
checkActive();
return this.attributes.get(name);
}
public Enumeration<String> getAttributeNames() {
checkActive();
return new Vector<String>(this.attributes.keySet()).elements();
}
public void setParameters(Map<String, String[]> parameters) {
Assert.notNull(parameters, "Parameters Map must not be null");
this.parameters.clear();
this.parameters.putAll(parameters);
}
public void setParameter(String key, String value) {
Assert.notNull(key, "Parameter key must be null");
Assert.notNull(value, "Parameter value must not be null");
this.parameters.put(key, new String[] { value });
}
public void setParameter(String key, String[] values) {
Assert.notNull(key, "Parameter key must be null");
Assert.notNull(values, "Parameter values must not be null");
this.parameters.put(key, values);
}
public void addParameter(String name, String value) {
addParameter(name, new String[] { value });
}
public void addParameter(String name, String[] values) {
String[] oldArr = this.parameters.get(name);
if (oldArr != null) {
String[] newArr = new String[oldArr.length + values.length];
System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
System.arraycopy(values, 0, newArr, oldArr.length, values.length);
this.parameters.put(name, newArr);
}
else {
this.parameters.put(name, values);
}
}
public String getParameter(String name) {
String[] arr = this.parameters.get(name);
return (arr != null && arr.length > 0 ? arr[0] : null);
}
public Enumeration<String> getParameterNames() {
return Collections.enumeration(this.parameters.keySet());
}
public String[] getParameterValues(String name) {
return this.parameters.get(name);
}
public Map<String, String[]> getParameterMap() {
return Collections.unmodifiableMap(this.parameters);
}
public void setSecure(boolean secure) {
this.secure = secure;
}
public boolean isSecure() {
return this.secure;
}
public void setAttribute(String name, Object value) {
checkActive();
if (value != null) {
this.attributes.put(name, value);
}
else {
this.attributes.remove(name);
}
}
public void removeAttribute(String name) {
checkActive();
this.attributes.remove(name);
}
public String getRequestedSessionId() {
PortletSession session = this.getPortletSession();
return (session != null ? session.getId() : null);
}
public void setRequestedSessionIdValid(boolean requestedSessionIdValid) {
this.requestedSessionIdValid = requestedSessionIdValid;
}
public boolean isRequestedSessionIdValid() {
return this.requestedSessionIdValid;
}
public void addResponseContentType(String responseContentType) {
this.responseContentTypes.add(responseContentType);
}
public void addPreferredResponseContentType(String responseContentType) {
this.responseContentTypes.add(0, responseContentType);
}
public String getResponseContentType() {
return this.responseContentTypes.get(0);
}
public Enumeration<String> getResponseContentTypes() {
return Collections.enumeration(this.responseContentTypes);
}
public void addLocale(Locale locale) {
this.locales.add(locale);
}
public void addPreferredLocale(Locale locale) {
this.locales.add(0, locale);
}
public Locale getLocale() {
return this.locales.get(0);
}
public Enumeration<Locale> getLocales() {
return Collections.enumeration(this.locales);
}
public void setScheme(String scheme) {
this.scheme = scheme;
}
public String getScheme() {
return this.scheme;
}
public void setServerName(String serverName) {
this.serverName = serverName;
}
public String getServerName() {
return this.serverName;
}
public void setServerPort(int serverPort) {
this.serverPort = serverPort;
}
public int getServerPort() {
return this.serverPort;
}
public void setWindowID(String windowID) {
this.windowID = windowID;
}
public String getWindowID() {
return this.windowID;
}
public void setCookies(Cookie... cookies) {
this.cookies = cookies;
}
public Cookie[] getCookies() {
return this.cookies;
}
public Map<String, String[]> getPrivateParameterMap() {
if (!this.publicParameterNames.isEmpty()) {
Map<String, String[]> filtered = new LinkedHashMap<String, String[]>();
for (String key : this.parameters.keySet()) {
if (!this.publicParameterNames.contains(key)) {
filtered.put(key, this.parameters.get(key));
}
}
return filtered;
}
else {
return Collections.unmodifiableMap(this.parameters);
}
}
public Map<String, String[]> getPublicParameterMap() {
if (!this.publicParameterNames.isEmpty()) {
Map<String, String[]> filtered = new LinkedHashMap<String, String[]>();
for (String key : this.parameters.keySet()) {
if (this.publicParameterNames.contains(key)) {
filtered.put(key, this.parameters.get(key));
}
}
return filtered;
}
else {
return Collections.emptyMap();
}
}
public void registerPublicParameter(String name) {
this.publicParameterNames.add(name);
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.IOException;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.portlet.PortletRequestDispatcher} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletRequestDispatcher implements PortletRequestDispatcher {
private final Log logger = LogFactory.getLog(getClass());
private final String url;
/**
* Create a new MockPortletRequestDispatcher for the given URL.
* @param url the URL to dispatch to.
*/
public MockPortletRequestDispatcher(String url) {
Assert.notNull(url, "URL must not be null");
this.url = url;
}
public void include(RenderRequest request, RenderResponse response) throws PortletException, IOException {
include((PortletRequest) request, (PortletResponse) response);
}
public void include(PortletRequest request, PortletResponse response) throws PortletException, IOException {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
if (!(response instanceof MockMimeResponse)) {
throw new IllegalArgumentException("MockPortletRequestDispatcher requires MockMimeResponse");
}
((MockMimeResponse) response).setIncludedUrl(this.url);
if (logger.isDebugEnabled()) {
logger.debug("MockPortletRequestDispatcher: including URL [" + this.url + "]");
}
}
public void forward(PortletRequest request, PortletResponse response) throws PortletException, IOException {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
if (!(response instanceof MockMimeResponse)) {
throw new IllegalArgumentException("MockPortletRequestDispatcher requires MockMimeResponse");
}
((MockMimeResponse) response).setForwardedUrl(this.url);
if (logger.isDebugEnabled()) {
logger.debug("MockPortletRequestDispatcher: forwarding to URL [" + this.url + "]");
}
}
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.portlet.PortalContext;
import javax.portlet.PortletResponse;
import javax.servlet.http.Cookie;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.portlet.PortletResponse} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletResponse implements PortletResponse {
private final PortalContext portalContext;
private final Map<String, String[]> properties = new LinkedHashMap<String, String[]>();
private String namespace = "";
private final Set<Cookie> cookies = new LinkedHashSet<Cookie>();
private final Map<String, Element[]> xmlProperties = new LinkedHashMap<String, Element[]>();
private Document xmlDocument;
/**
* Create a new MockPortletResponse with a default {@link MockPortalContext}.
* @see MockPortalContext
*/
public MockPortletResponse() {
this(null);
}
/**
* Create a new MockPortletResponse.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
*/
public MockPortletResponse(PortalContext portalContext) {
this.portalContext = (portalContext != null ? portalContext : new MockPortalContext());
}
/**
* Return the PortalContext that this MockPortletResponse runs in,
* defining the supported PortletModes and WindowStates.
*/
public PortalContext getPortalContext() {
return this.portalContext;
}
//---------------------------------------------------------------------
// PortletResponse methods
//---------------------------------------------------------------------
public void addProperty(String key, String value) {
Assert.notNull(key, "Property key must not be null");
String[] oldArr = this.properties.get(key);
if (oldArr != null) {
String[] newArr = new String[oldArr.length + 1];
System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
newArr[oldArr.length] = value;
this.properties.put(key, newArr);
}
else {
this.properties.put(key, new String[] {value});
}
}
public void setProperty(String key, String value) {
Assert.notNull(key, "Property key must not be null");
this.properties.put(key, new String[] {value});
}
public Set<String> getPropertyNames() {
return Collections.unmodifiableSet(this.properties.keySet());
}
public String getProperty(String key) {
Assert.notNull(key, "Property key must not be null");
String[] arr = this.properties.get(key);
return (arr != null && arr.length > 0 ? arr[0] : null);
}
public String[] getProperties(String key) {
Assert.notNull(key, "Property key must not be null");
return this.properties.get(key);
}
public String encodeURL(String path) {
return path;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getNamespace() {
return this.namespace;
}
public void addProperty(Cookie cookie) {
Assert.notNull(cookie, "Cookie must not be null");
this.cookies.add(cookie);
}
public Cookie[] getCookies() {
return this.cookies.toArray(new Cookie[this.cookies.size()]);
}
public Cookie getCookie(String name) {
Assert.notNull(name, "Cookie name must not be null");
for (Cookie cookie : this.cookies) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
return null;
}
public void addProperty(String key, Element value) {
Assert.notNull(key, "Property key must not be null");
Element[] oldArr = this.xmlProperties.get(key);
if (oldArr != null) {
Element[] newArr = new Element[oldArr.length + 1];
System.arraycopy(oldArr, 0, newArr, 0, oldArr.length);
newArr[oldArr.length] = value;
this.xmlProperties.put(key, newArr);
}
else {
this.xmlProperties.put(key, new Element[] {value});
}
}
public Set<String> getXmlPropertyNames() {
return Collections.unmodifiableSet(this.xmlProperties.keySet());
}
public Element getXmlProperty(String key) {
Assert.notNull(key, "Property key must not be null");
Element[] arr = this.xmlProperties.get(key);
return (arr != null && arr.length > 0 ? arr[0] : null);
}
public Element[] getXmlProperties(String key) {
Assert.notNull(key, "Property key must not be null");
return this.xmlProperties.get(key);
}
public Element createElement(String tagName) throws DOMException {
if (this.xmlDocument == null) {
try {
this.xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
}
catch (ParserConfigurationException ex) {
throw new DOMException(DOMException.INVALID_STATE_ERR, ex.toString());
}
}
return this.xmlDocument.createElement(tagName);
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright 2002-2010 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.mock.web.portlet;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import javax.portlet.PortletContext;
import javax.portlet.PortletSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import org.springframework.mock.web.MockHttpSession;
/**
* Mock implementation of the {@link javax.portlet.PortletSession} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletSession implements PortletSession {
private static int nextId = 1;
private final String id = Integer.toString(nextId++);
private final long creationTime = System.currentTimeMillis();
private int maxInactiveInterval;
private long lastAccessedTime = System.currentTimeMillis();
private final PortletContext portletContext;
private final Map<String, Object> portletAttributes = new HashMap<String, Object>();
private final Map<String, Object> applicationAttributes = new HashMap<String, Object>();
private boolean invalid = false;
private boolean isNew = true;
/**
* Create a new MockPortletSession with a default {@link MockPortletContext}.
* @see MockPortletContext
*/
public MockPortletSession() {
this(null);
}
/**
* Create a new MockPortletSession.
* @param portletContext the PortletContext that the session runs in
*/
public MockPortletSession(PortletContext portletContext) {
this.portletContext = (portletContext != null ? portletContext : new MockPortletContext());
}
public Object getAttribute(String name) {
return this.portletAttributes.get(name);
}
public Object getAttribute(String name, int scope) {
if (scope == PortletSession.PORTLET_SCOPE) {
return this.portletAttributes.get(name);
}
else if (scope == PortletSession.APPLICATION_SCOPE) {
return this.applicationAttributes.get(name);
}
return null;
}
public Enumeration<String> getAttributeNames() {
return new Vector<String>(this.portletAttributes.keySet()).elements();
}
public Enumeration<String> getAttributeNames(int scope) {
if (scope == PortletSession.PORTLET_SCOPE) {
return new Vector<String>(this.portletAttributes.keySet()).elements();
}
else if (scope == PortletSession.APPLICATION_SCOPE) {
return new Vector<String>(this.applicationAttributes.keySet()).elements();
}
return null;
}
public long getCreationTime() {
return this.creationTime;
}
public String getId() {
return this.id;
}
public void access() {
this.lastAccessedTime = System.currentTimeMillis();
setNew(false);
}
public long getLastAccessedTime() {
return this.lastAccessedTime;
}
public int getMaxInactiveInterval() {
return this.maxInactiveInterval;
}
/**
* Clear all of this session's attributes.
*/
public void clearAttributes() {
doClearAttributes(this.portletAttributes);
doClearAttributes(this.applicationAttributes);
}
protected void doClearAttributes(Map<String, Object> attributes) {
for (Iterator<Map.Entry<String, Object>> it = attributes.entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Object> entry = it.next();
String name = entry.getKey();
Object value = entry.getValue();
it.remove();
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueUnbound(
new HttpSessionBindingEvent(new MockHttpSession(), name, value));
}
}
}
public void invalidate() {
this.invalid = true;
clearAttributes();
}
public boolean isInvalid() {
return this.invalid;
}
public void setNew(boolean value) {
this.isNew = value;
}
public boolean isNew() {
return this.isNew;
}
public void removeAttribute(String name) {
this.portletAttributes.remove(name);
}
public void removeAttribute(String name, int scope) {
if (scope == PortletSession.PORTLET_SCOPE) {
this.portletAttributes.remove(name);
}
else if (scope == PortletSession.APPLICATION_SCOPE) {
this.applicationAttributes.remove(name);
}
}
public void setAttribute(String name, Object value) {
if (value != null) {
this.portletAttributes.put(name, value);
}
else {
this.portletAttributes.remove(name);
}
}
public void setAttribute(String name, Object value, int scope) {
if (scope == PortletSession.PORTLET_SCOPE) {
if (value != null) {
this.portletAttributes.put(name, value);
}
else {
this.portletAttributes.remove(name);
}
}
else if (scope == PortletSession.APPLICATION_SCOPE) {
if (value != null) {
this.applicationAttributes.put(name, value);
}
else {
this.applicationAttributes.remove(name);
}
}
}
public void setMaxInactiveInterval(int interval) {
this.maxInactiveInterval = interval;
}
public PortletContext getPortletContext() {
return this.portletContext;
}
public Map<String, Object> getAttributeMap() {
return Collections.unmodifiableMap(this.portletAttributes);
}
public Map<String, Object> getAttributeMap(int scope) {
if (scope == PortletSession.PORTLET_SCOPE) {
return Collections.unmodifiableMap(this.portletAttributes);
}
else if (scope == PortletSession.APPLICATION_SCOPE) {
return Collections.unmodifiableMap(this.applicationAttributes);
}
else {
return Collections.emptyMap();
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.util.Map;
import javax.portlet.PortalContext;
import javax.portlet.PortletMode;
import javax.portlet.PortletModeException;
import javax.portlet.PortletURL;
import javax.portlet.WindowState;
import javax.portlet.WindowStateException;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Mock implementation of the {@link javax.portlet.PortletURL} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockPortletURL extends MockBaseURL implements PortletURL {
public static final String URL_TYPE_RENDER = "render";
public static final String URL_TYPE_ACTION = "action";
private final PortalContext portalContext;
private final String urlType;
private WindowState windowState;
private PortletMode portletMode;
/**
* Create a new MockPortletURL for the given URL type.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
* @param urlType the URL type, for example "render" or "action"
* @see #URL_TYPE_RENDER
* @see #URL_TYPE_ACTION
*/
public MockPortletURL(PortalContext portalContext, String urlType) {
Assert.notNull(portalContext, "PortalContext is required");
this.portalContext = portalContext;
this.urlType = urlType;
}
//---------------------------------------------------------------------
// PortletURL methods
//---------------------------------------------------------------------
public void setWindowState(WindowState windowState) throws WindowStateException {
if (!CollectionUtils.contains(this.portalContext.getSupportedWindowStates(), windowState)) {
throw new WindowStateException("WindowState not supported", windowState);
}
this.windowState = windowState;
}
public WindowState getWindowState() {
return this.windowState;
}
public void setPortletMode(PortletMode portletMode) throws PortletModeException {
if (!CollectionUtils.contains(this.portalContext.getSupportedPortletModes(), portletMode)) {
throw new PortletModeException("PortletMode not supported", portletMode);
}
this.portletMode = portletMode;
}
public PortletMode getPortletMode() {
return this.portletMode;
}
public void removePublicRenderParameter(String name) {
this.parameters.remove(name);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(encodeParameter("urlType", this.urlType));
if (this.windowState != null) {
sb.append(";").append(encodeParameter("windowState", this.windowState.toString()));
}
if (this.portletMode != null) {
sb.append(";").append(encodeParameter("portletMode", this.portletMode.toString()));
}
for (Map.Entry<String, String[]> entry : this.parameters.entrySet()) {
sb.append(";").append(encodeParameter("param_" + entry.getKey(), entry.getValue()));
}
return (isSecure() ? "https:" : "http:") +
"//localhost/mockportlet?" + sb.toString();
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import javax.portlet.PortalContext;
import javax.portlet.PortletContext;
import javax.portlet.PortletMode;
import javax.portlet.RenderRequest;
import javax.portlet.WindowState;
/**
* Mock implementation of the {@link javax.portlet.RenderRequest} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockRenderRequest extends MockPortletRequest implements RenderRequest {
/**
* Create a new MockRenderRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @see MockPortalContext
* @see MockPortletContext
*/
public MockRenderRequest() {
super();
}
/**
* Create a new MockRenderRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @param portletMode the mode that the portlet runs in
*/
public MockRenderRequest(PortletMode portletMode) {
super();
setPortletMode(portletMode);
}
/**
* Create a new MockRenderRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @param portletMode the mode that the portlet runs in
* @param windowState the window state to run the portlet in
*/
public MockRenderRequest(PortletMode portletMode, WindowState windowState) {
super();
setPortletMode(portletMode);
setWindowState(windowState);
}
/**
* Create a new MockRenderRequest with a default {@link MockPortalContext}.
* @param portletContext the PortletContext that the request runs in
*/
public MockRenderRequest(PortletContext portletContext) {
super(portletContext);
}
/**
* Create a new MockRenderRequest.
* @param portalContext the PortletContext that the request runs in
* @param portletContext the PortletContext that the request runs in
*/
public MockRenderRequest(PortalContext portalContext, PortletContext portletContext) {
super(portalContext, portletContext);
}
@Override
protected String getLifecyclePhase() {
return RENDER_PHASE;
}
public String getETag() {
return getProperty(RenderRequest.ETAG);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.util.Collection;
import javax.portlet.PortalContext;
import javax.portlet.PortletMode;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
/**
* Mock implementation of the {@link javax.portlet.RenderResponse} interface.
*
* @author John A. Lewis
* @author Juergen Hoeller
* @since 2.0
*/
public class MockRenderResponse extends MockMimeResponse implements RenderResponse {
private String title;
private Collection<PortletMode> nextPossiblePortletModes;
/**
* Create a new MockRenderResponse with a default {@link MockPortalContext}.
* @see MockPortalContext
*/
public MockRenderResponse() {
super();
}
/**
* Create a new MockRenderResponse.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
*/
public MockRenderResponse(PortalContext portalContext) {
super(portalContext);
}
/**
* Create a new MockRenderResponse.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
* @param request the corresponding render request that this response
* is generated for
*/
public MockRenderResponse(PortalContext portalContext, RenderRequest request) {
super(portalContext, request);
}
//---------------------------------------------------------------------
// RenderResponse methods
//---------------------------------------------------------------------
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
public void setNextPossiblePortletModes(Collection<PortletMode> portletModes) {
this.nextPossiblePortletModes = portletModes;
}
public Collection<PortletMode> getNextPossiblePortletModes() {
return this.nextPossiblePortletModes;
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.portlet.PortalContext;
import javax.portlet.PortletContext;
import javax.portlet.RenderRequest;
import javax.portlet.ResourceRequest;
/**
* Mock implementation of the {@link javax.portlet.ResourceRequest} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockResourceRequest extends MockClientDataRequest implements ResourceRequest {
private String resourceID;
private String cacheability;
private final Map<String, String[]> privateRenderParameterMap = new LinkedHashMap<String, String[]>();
/**
* Create a new MockResourceRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @see org.springframework.mock.web.portlet.MockPortalContext
* @see org.springframework.mock.web.portlet.MockPortletContext
*/
public MockResourceRequest() {
super();
}
/**
* Create a new MockResourceRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @param resourceID the resource id for this request
*/
public MockResourceRequest(String resourceID) {
super();
this.resourceID = resourceID;
}
/**
* Create a new MockResourceRequest with a default {@link MockPortalContext}
* and a default {@link MockPortletContext}.
* @param url the resource URL for this request
*/
public MockResourceRequest(MockResourceURL url) {
super();
this.resourceID = url.getResourceID();
this.cacheability = url.getCacheability();
}
/**
* Create a new MockResourceRequest with a default {@link MockPortalContext}.
* @param portletContext the PortletContext that the request runs in
*/
public MockResourceRequest(PortletContext portletContext) {
super(portletContext);
}
/**
* Create a new MockResourceRequest.
* @param portalContext the PortalContext that the request runs in
* @param portletContext the PortletContext that the request runs in
*/
public MockResourceRequest(PortalContext portalContext, PortletContext portletContext) {
super(portalContext, portletContext);
}
@Override
protected String getLifecyclePhase() {
return RESOURCE_PHASE;
}
public void setResourceID(String resourceID) {
this.resourceID = resourceID;
}
public String getResourceID() {
return this.resourceID;
}
public void setCacheability(String cacheLevel) {
this.cacheability = cacheLevel;
}
public String getCacheability() {
return this.cacheability;
}
public String getETag() {
return getProperty(RenderRequest.ETAG);
}
public void addPrivateRenderParameter(String key, String value) {
this.privateRenderParameterMap.put(key, new String[] {value});
}
public void addPrivateRenderParameter(String key, String[] values) {
this.privateRenderParameterMap.put(key, values);
}
public Map<String, String[]> getPrivateRenderParameterMap() {
return Collections.unmodifiableMap(this.privateRenderParameterMap);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import javax.portlet.ResourceResponse;
/**
* Mock implementation of the {@link javax.portlet.ResourceResponse} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockResourceResponse extends MockMimeResponse implements ResourceResponse {
private int contentLength = 0;
public void setContentLength(int len) {
this.contentLength = len;
}
public int getContentLength() {
return this.contentLength;
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.util.Map;
import javax.portlet.ResourceURL;
/**
* Mock implementation of the {@link javax.portlet.ResourceURL} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockResourceURL extends MockBaseURL implements ResourceURL {
private String resourceID;
private String cacheability;
//---------------------------------------------------------------------
// ResourceURL methods
//---------------------------------------------------------------------
public void setResourceID(String resourceID) {
this.resourceID = resourceID;
}
public String getResourceID() {
return this.resourceID;
}
public void setCacheability(String cacheLevel) {
this.cacheability = cacheLevel;
}
public String getCacheability() {
return this.cacheability;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(encodeParameter("resourceID", this.resourceID));
if (this.cacheability != null) {
sb.append(";").append(encodeParameter("cacheability", this.cacheability));
}
for (Map.Entry<String, String[]> entry : this.parameters.entrySet()) {
sb.append(";").append(encodeParameter("param_" + entry.getKey(), entry.getValue()));
}
return (isSecure() ? "https:" : "http:") +
"//localhost/mockportlet?" + sb.toString();
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.portlet.PortalContext;
import javax.portlet.PortletMode;
import javax.portlet.PortletModeException;
import javax.portlet.StateAwareResponse;
import javax.portlet.WindowState;
import javax.portlet.WindowStateException;
import javax.xml.namespace.QName;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Mock implementation of the {@link javax.portlet.StateAwareResponse} interface.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class MockStateAwareResponse extends MockPortletResponse implements StateAwareResponse {
private WindowState windowState;
private PortletMode portletMode;
private final Map<String, String[]> renderParameters = new LinkedHashMap<String, String[]>();
private final Map<QName, Serializable> events = new HashMap<QName, Serializable>();
/**
* Create a new MockActionResponse with a default {@link MockPortalContext}.
* @see org.springframework.mock.web.portlet.MockPortalContext
*/
public MockStateAwareResponse() {
super();
}
/**
* Create a new MockActionResponse.
* @param portalContext the PortalContext defining the supported
* PortletModes and WindowStates
*/
public MockStateAwareResponse(PortalContext portalContext) {
super(portalContext);
}
public void setWindowState(WindowState windowState) throws WindowStateException {
if (!CollectionUtils.contains(getPortalContext().getSupportedWindowStates(), windowState)) {
throw new WindowStateException("WindowState not supported", windowState);
}
this.windowState = windowState;
}
public WindowState getWindowState() {
return this.windowState;
}
public void setPortletMode(PortletMode portletMode) throws PortletModeException {
if (!CollectionUtils.contains(getPortalContext().getSupportedPortletModes(), portletMode)) {
throw new PortletModeException("PortletMode not supported", portletMode);
}
this.portletMode = portletMode;
}
public PortletMode getPortletMode() {
return this.portletMode;
}
public void setRenderParameters(Map<String, String[]> parameters) {
Assert.notNull(parameters, "Parameters Map must not be null");
this.renderParameters.clear();
this.renderParameters.putAll(parameters);
}
public void setRenderParameter(String key, String value) {
Assert.notNull(key, "Parameter key must not be null");
Assert.notNull(value, "Parameter value must not be null");
this.renderParameters.put(key, new String[] {value});
}
public void setRenderParameter(String key, String[] values) {
Assert.notNull(key, "Parameter key must not be null");
Assert.notNull(values, "Parameter values must not be null");
this.renderParameters.put(key, values);
}
public String getRenderParameter(String key) {
Assert.notNull(key, "Parameter key must not be null");
String[] arr = this.renderParameters.get(key);
return (arr != null && arr.length > 0 ? arr[0] : null);
}
public String[] getRenderParameterValues(String key) {
Assert.notNull(key, "Parameter key must not be null");
return this.renderParameters.get(key);
}
public Iterator<String> getRenderParameterNames() {
return this.renderParameters.keySet().iterator();
}
public Map<String, String[]> getRenderParameterMap() {
return Collections.unmodifiableMap(this.renderParameters);
}
public void removePublicRenderParameter(String name) {
this.renderParameters.remove(name);
}
public void setEvent(QName name, Serializable value) {
this.events.put(name, value);
}
public void setEvent(String name, Serializable value) {
this.events.put(new QName(name), value);
}
public Iterator<QName> getEventNames() {
return this.events.keySet().iterator();
}
public Serializable getEvent(QName name) {
return this.events.get(name);
}
public Serializable getEvent(String name) {
return this.events.get(new QName(name));
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2002-2009 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.mock.web.portlet;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequestDispatcher;
import javax.servlet.ServletContext;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.portlet.PortletContext} interface,
* wrapping an underlying {@link javax.servlet.ServletContext}.
*
* @author Juergen Hoeller
* @since 3.0
* @see MockPortletContext
*/
public class ServletWrappingPortletContext implements PortletContext {
private final ServletContext servletContext;
/**
* Create a new PortletContext wrapping the given ServletContext.
* @param servletContext the ServletContext to wrap
*/
public ServletWrappingPortletContext(ServletContext servletContext) {
Assert.notNull(servletContext, "ServletContext must not be null");
this.servletContext = servletContext;
}
/**
* Return the underlying ServletContext that this PortletContext wraps.
*/
public final ServletContext getServletContext() {
return this.servletContext;
}
public String getServerInfo() {
return this.servletContext.getServerInfo();
}
public PortletRequestDispatcher getRequestDispatcher(String path) {
return null;
}
public PortletRequestDispatcher getNamedDispatcher(String name) {
return null;
}
public InputStream getResourceAsStream(String path) {
return this.servletContext.getResourceAsStream(path);
}
public int getMajorVersion() {
return 2;
}
public int getMinorVersion() {
return 0;
}
public String getMimeType(String file) {
return this.servletContext.getMimeType(file);
}
public String getRealPath(String path) {
return this.servletContext.getRealPath(path);
}
@SuppressWarnings("unchecked")
public Set<String> getResourcePaths(String path) {
return this.servletContext.getResourcePaths(path);
}
public URL getResource(String path) throws MalformedURLException {
return this.servletContext.getResource(path);
}
public Object getAttribute(String name) {
return this.servletContext.getAttribute(name);
}
@SuppressWarnings("unchecked")
public Enumeration<String> getAttributeNames() {
return this.servletContext.getAttributeNames();
}
public String getInitParameter(String name) {
return this.servletContext.getInitParameter(name);
}
@SuppressWarnings("unchecked")
public Enumeration<String> getInitParameterNames() {
return this.servletContext.getInitParameterNames();
}
public void log(String msg) {
this.servletContext.log(msg);
}
public void log(String message, Throwable throwable) {
this.servletContext.log(message, throwable);
}
public void removeAttribute(String name) {
this.servletContext.removeAttribute(name);
}
public void setAttribute(String name, Object object) {
this.servletContext.setAttribute(name, object);
}
public String getPortletContextName() {
return this.servletContext.getServletContextName();
}
public Enumeration<String> getContainerRuntimeOptions() {
return Collections.enumeration(new HashSet<String>());
}
}

View File

@@ -0,0 +1,14 @@
/**
*
* A comprehensive set of Portlet API 2.0 mock objects,
* targeted at usage with Spring's web MVC framework.
* Useful for testing web contexts and controllers.
*
* <p>More convenient to use than dynamic mock objects
* (<a href="http://www.easymock.org">EasyMock</a>) or
* existing Portlet API mock objects.
*
*/
package org.springframework.mock.web.portlet;

View File

@@ -0,0 +1,293 @@
/*
* 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.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
/**
* <p>
* Convenient superclass for JUnit 3.8 based tests depending on a Spring
* context. The test instance itself is populated by Dependency Injection.
* </p>
* <p>
* Really for integration testing, not unit testing. You should <i>not</i>
* normally use the Spring container for unit tests: simply populate your POJOs
* in plain JUnit tests!
* </p>
* <p>
* This supports two modes of populating the test:
* </p>
* <ul>
* <li>Via Setter Dependency Injection. Simply express dependencies on objects
* in the test fixture, and they will be satisfied by autowiring by type.
* <li>Via Field Injection. Declare protected variables of the required type
* which match named beans in the context. This is autowire by name, rather than
* type. This approach is based on an approach originated by Ara Abrahmian.
* Setter Dependency Injection is the default: set the
* <code>populateProtectedVariables</code> property to <code>true</code> in
* the constructor to switch on Field Injection.
* </ul>
*
* @author Rod Johnson
* @author Rob Harrop
* @author Rick Evans
* @author Sam Brannen
* @since 1.1.1
* @see #setDirty
* @see #contextKey
* @see #getContext
* @see #getConfigLocations
* @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
* ({@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests})
*/
@Deprecated
@SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class AbstractDependencyInjectionSpringContextTests extends AbstractSingleSpringContextTests {
/**
* Constant that indicates no autowiring at all.
*
* @see #setAutowireMode
*/
public static final int AUTOWIRE_NO = 0;
/**
* Constant that indicates autowiring bean properties by name.
*
* @see #setAutowireMode
*/
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
/**
* Constant that indicates autowiring bean properties by type.
*
* @see #setAutowireMode
*/
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
private boolean populateProtectedVariables = false;
private int autowireMode = AUTOWIRE_BY_TYPE;
private boolean dependencyCheck = true;
private String[] managedVariableNames;
/**
* Default constructor for AbstractDependencyInjectionSpringContextTests.
*/
public AbstractDependencyInjectionSpringContextTests() {
}
/**
* Constructor for AbstractDependencyInjectionSpringContextTests with a
* JUnit name.
* @param name the name of this text fixture
*/
public AbstractDependencyInjectionSpringContextTests(String name) {
super(name);
}
/**
* Set whether to populate protected variables of this test case. Default is
* <code>false</code>.
*/
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.
* <p>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.
* <p>The default is <code>true</code>, 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.
* <p>Note: if the {@link ApplicationContext} for this test instance has not
* been configured (e.g., is <code>null</code>), dependency injection
* will naturally <strong>not</strong> 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).
* <p>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).
* <p>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;
}
}
}
}

View File

@@ -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;
/**
* <p>
* Abstract JUnit 3.8 test class that holds and exposes a single Spring
* {@link org.springframework.context.ApplicationContext ApplicationContext}.
* </p>
* <p>
* This class will cache contexts based on a <i>context key</i>: 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.
* </p>
* <p>
* 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()}.
* </p>
* <p>
* 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.
* </p>
* <p>
* <b>WARNING:</b> When doing integration tests from within Eclipse, only use
* classpath resource URLs. Else, you may see misleading failures when changing
* context locations.
* </p>
*
* @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.junit4.AbstractJUnit4SpringContextTests})
*/
@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. Override <code>onSetUp</code> 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.
* <p>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 <code>setUp()</code>
* method, which is final in this class.
* <p>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 <code>onTearDown</code> 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()}.
* <p>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.
* <p>If you override {@link #contextKey()}, you will typically have to
* override this method as well, being able to handle the key type that
* <code>contextKey()</code> returns.
* @see #getConfigLocations()
*/
protected ConfigurableApplicationContext loadContext(Object key) throws Exception {
return loadContextLocations((String[]) key);
}
/**
* Load a Spring ApplicationContext from the given config locations.
* <p>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.
* <p>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 <code>locations</code> 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.
* <p>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.
* <p>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}.
* <p>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.
* <p>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.
* <p>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.
* <p>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".
* <p>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.
* <p>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".
* <p>The default implementation simply returns <code>null</code>.
* @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
* <code>null</code>.
*/
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;
}
}

View File

@@ -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;
/**
* <p>
* Superclass for JUnit 3.8 test cases using Spring
* {@link org.springframework.context.ApplicationContext ApplicationContexts}.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* Normally you won't extend this class directly but rather one of its
* subclasses.
* </p>
*
* @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.junit4.AbstractJUnit4SpringContextTests})
*/
@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 Map<String, ConfigurableApplicationContext> contextKeyToContextMap =
new HashMap<String, ConfigurableApplicationContext>();
/**
* Default constructor for AbstractSpringContextTests.
*/
public AbstractSpringContextTests() {
}
/**
* Constructor for AbstractSpringContextTests with a JUnit name.
*/
public AbstractSpringContextTests(String name) {
super(name);
}
/**
* Explicitly add an ApplicationContext instance under a given key.
* <p>This 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 <code>key</code> is <em>empty</em>.
* <p>By default, <code>null</code> values, empty strings, and zero-length
* arrays are considered <em>empty</em>.
* @param key the context key to check
* @return <code>true</code> 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 <code>null</code>.
* @return the corresponding ApplicationContext instance (potentially cached),
* or <code>null</code> if the provided <code>key</code> is <em>empty</em>
*/
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.
* <p>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.
* <p>To be implemented by subclasses.
* @param key the context key
* @return the corresponding ApplicationContext instance (new)
*/
protected abstract ConfigurableApplicationContext loadContext(Object key) throws Exception;
}

View File

@@ -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;
/**
* 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.
*
* <p>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.junit4.AbstractTransactionalJUnit4SpringContextTests})
*/
@Deprecated
@SuppressWarnings({ "unchecked", "rawtypes" })
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
* <code>setComplete()</code> 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 <i>permanent</i> 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.
* <p>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.
* <p><b>Do not use this method to execute DDL if you expect rollback.</b>
* @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);
}
}
}

View File

@@ -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.
*
* <p>This is useful in a range of circumstances, allowing the following benefits:
* <ul>
* <li>Ability to delete or insert any data in the database, without affecting
* other tests
* <li>Providing a transactional context for any code requiring a transaction
* <li>Ability to write anything to the database without any need to clean up.
* </ul>
*
* <p>This class is typically very fast, compared to traditional setup/teardown
* scripts.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p><b>This base class can also be used without transaction management, if no
* PlatformTransactionManager bean is found in the context provided.</b> 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.junit4.AbstractTransactionalJUnit4SpringContextTests})
*/
@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.
* <p>
* 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 <em>default rollback</em> flag for this test.
* @see #setDefaultRollback(boolean)
* @return The <em>default rollback</em> flag.
*/
protected boolean isDefaultRollback() {
return this.defaultRollback;
}
/**
* Determines whether or not to rollback transactions for the current test.
* <p>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.
* <p>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 <code>super.onSetUp()</code> 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, <i>before</i> the transaction created by
* this class. Only invoked if there <i>is</i> 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, <i>within</i> the transaction created by
* this class.
* <p><b>NB:</b> Not called if there is no transaction management, due to no
* transaction manager being provided in the context.
* <p>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.
* <p>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 <code>super.onTearDown()</code> as
* part of your method implementation.
* <p>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 <i>not</i> 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 <i>still active</i> 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.
* <p><b>NB:</b> 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 <i>not active anymore</i>.
* @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 <code>complete</code> and {@link #isRollback() rollback} flags.
* <p>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() + "].");
}
}
}

View File

@@ -0,0 +1,255 @@
/*
* 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;
/**
* Simple method object encapsulation of the 'test-for-Exception' scenario (for JUnit).
*
* <p>Used like so:
*
* <pre class="code">
* // the class under test
* public class Foo {
* public void someBusinessLogic(String name) {
* if (name == null) {
* throw new IllegalArgumentException("The 'name' argument is required");
* }
* // rest of business logic here...
* }
* }</pre>
*
* The test for the above bad argument path can be expressed using the
* {@link AssertThrows} class like so:
*
* <pre class="code">
* public class FooTest {
* public void testSomeBusinessLogicBadArgumentPath() {
* new AssertThrows(IllegalArgumentException.class) {
* public void test() {
* new Foo().someBusinessLogic(null);
* }
* }.runTest();
* }
* }</pre>
*
* This will result in the test passing if the <code>Foo.someBusinessLogic(..)</code>
* method threw an {@link java.lang.IllegalArgumentException}; if it did not, the
* test would fail with the following message:
*
* <pre class="code">
* "Must have thrown a [class java.lang.IllegalArgumentException]"</pre>
*
* If the <b>wrong</b> type of {@link java.lang.Exception} was thrown, the
* test will also fail, this time with a message similar to the following:
*
* <pre class="code">
* "junit.framework.AssertionFailedError: Was expecting a [class java.lang.UnsupportedOperationException] to be thrown, but instead a [class java.lang.IllegalArgumentException] was thrown"</pre>
*
* The test for the correct {@link java.lang.Exception} respects polymorphism,
* so you can test that any old {@link java.lang.Exception} is thrown like so:
*
* <pre class="code">
* public class FooTest {
* public void testSomeBusinessLogicBadArgumentPath() {
* // any Exception will do...
* new AssertThrows(Exception.class) {
* public void test() {
* new Foo().someBusinessLogic(null);
* }
* }.runTest();
* }
* }</pre>
*
* Intended for use with JUnit 4 and TestNG (as of Spring 3.0).
* You might want to compare this class with the
* {@link junit.extensions.ExceptionTestCase} class.
*
* @author Rick Evans
* @author Juergen Hoeller
* @since 2.0
* @deprecated favor use of JUnit 4's {@code @Test(expected=...)} support
*/
@Deprecated
@SuppressWarnings({ "unchecked", "rawtypes" })
public abstract class AssertThrows {
private final Class expectedException;
private String failureMessage;
private Exception actualException;
/**
* Create a new instance of the {@link AssertThrows} class.
* @param expectedException the {@link java.lang.Exception} expected to be
* thrown during the execution of the surrounding test
* @throws IllegalArgumentException if the supplied <code>expectedException</code> is
* <code>null</code>; or if said argument is not an {@link java.lang.Exception}-derived class
*/
public AssertThrows(Class expectedException) {
this(expectedException, null);
}
/**
* Create a new instance of the {@link AssertThrows} class.
* @param expectedException the {@link java.lang.Exception} expected to be
* thrown during the execution of the surrounding test
* @param failureMessage the extra, contextual failure message that will be
* included in the failure text if the text fails (can be <code>null</code>)
* @throws IllegalArgumentException if the supplied <code>expectedException</code> is
* <code>null</code>; or if said argument is not an {@link java.lang.Exception}-derived class
*/
public AssertThrows(Class expectedException, String failureMessage) {
if (expectedException == null) {
throw new IllegalArgumentException("The 'expectedException' argument is required");
}
if (!Exception.class.isAssignableFrom(expectedException)) {
throw new IllegalArgumentException(
"The 'expectedException' argument is not an Exception type (it obviously must be)");
}
this.expectedException = expectedException;
this.failureMessage = failureMessage;
}
/**
* Return the {@link java.lang.Exception} expected to be thrown during
* the execution of the surrounding test.
*/
protected Class getExpectedException() {
return this.expectedException;
}
/**
* Set the extra, contextual failure message that will be included
* in the failure text if the text fails.
*/
public void setFailureMessage(String failureMessage) {
this.failureMessage = failureMessage;
}
/**
* Return the extra, contextual failure message that will be included
* in the failure text if the text fails.
*/
protected String getFailureMessage() {
return this.failureMessage;
}
/**
* Subclass must override this <code>abstract</code> method and
* provide the test logic.
* @throws Exception if an error occurs during the execution of the
* aformentioned test logic
*/
public abstract void test() throws Exception;
/**
* The main template method that drives the running of the
* {@link #test() test logic} and the
* {@link #checkExceptionExpectations(Exception) checking} of the
* resulting (expected) {@link java.lang.Exception}.
* @see #test()
* @see #doFail()
* @see #checkExceptionExpectations(Exception)
*/
public void runTest() {
try {
test();
doFail();
}
catch (Exception actualException) {
this.actualException = actualException;
checkExceptionExpectations(actualException);
}
}
/**
* Template method called when the test fails; i.e. the expected
* {@link java.lang.Exception} is <b>not</b> thrown.
* <p>The default implementation simply fails the test via a call to
* {@link junit.framework.Assert#fail(String)}.
* <p>If you want to customise the failure message, consider overriding
* {@link #createMessageForNoExceptionThrown()}, and / or supplying an
* extra, contextual failure message via the appropriate constructor overload.
* @see #getFailureMessage()
*/
protected void doFail() {
throw new AssertionError(createMessageForNoExceptionThrown());
}
/**
* Creates the failure message used if the test fails
* (i.e. the expected exception is not thrown in the body of the test).
* @return the failure message used if the test fails
* @see #getFailureMessage()
*/
protected String createMessageForNoExceptionThrown() {
StringBuilder sb = new StringBuilder();
sb.append("Should have thrown a [").append(this.getExpectedException()).append("]");
if (getFailureMessage() != null) {
sb.append(": ").append(getFailureMessage());
}
return sb.toString();
}
/**
* Does the donkey work of checking (verifying) that the
* {@link java.lang.Exception} that was thrown in the body of a test is
* an instance of the {@link #getExpectedException()} class (or an
* instance of a subclass).
* <p>If you want to customise the failure message, consider overriding
* {@link #createMessageForWrongThrownExceptionType(Exception)}.
* @param actualException the {@link java.lang.Exception} that has been thrown
* in the body of a test method (will never be <code>null</code>)
*/
protected void checkExceptionExpectations(Exception actualException) {
if (!getExpectedException().isAssignableFrom(actualException.getClass())) {
AssertionError error =
new AssertionError(createMessageForWrongThrownExceptionType(actualException));
error.initCause(actualException);
throw error;
}
}
/**
* Creates the failure message used if the wrong type
* of {@link java.lang.Exception} is thrown in the body of the test.
* @param actualException the actual exception thrown
* @return the message for the given exception
*/
protected String createMessageForWrongThrownExceptionType(Exception actualException) {
StringBuilder sb = new StringBuilder();
sb.append("Was expecting a [").append(getExpectedException().getName());
sb.append("] to be thrown, but instead a [").append(actualException.getClass().getName());
sb.append("] was thrown.");
return sb.toString();
}
/**
* Expose the actual exception thrown from {@link #test}, if any.
* @return the actual exception, or <code>null</code> if none
*/
public final Exception getActualException() {
return this.actualException;
}
}

View File

@@ -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.junit4.AbstractJUnit4SpringContextTests})
*/
@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() + " is 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;
}
}

View File

@@ -0,0 +1,313 @@
/*
* 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;
/**
* <p>
* Java 5 specific subclass of
* {@link AbstractTransactionalDataSourceSpringContextTests}, exposing a
* {@link SimpleJdbcTemplate} and obeying annotations for transaction control.
* </p>
* <p>
* 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.
* </p>
* <p>
* The following list constitutes all annotations currently supported by
* AbstractAnnotationAwareTransactionalTests:
* </p>
* <ul>
* <li>{@link DirtiesContext @DirtiesContext}</li>
* <li>{@link ProfileValueSourceConfiguration @ProfileValueSourceConfiguration}</li>
* <li>{@link IfProfileValue @IfProfileValue}</li>
* <li>{@link ExpectedException @ExpectedException}</li>
* <li>{@link Timed @Timed}</li>
* <li>{@link Repeat @Repeat}</li>
* <li>{@link org.springframework.transaction.annotation.Transactional @Transactional}</li>
* <li>{@link NotTransactional @NotTransactional}</li>
* <li>{@link Rollback @Rollback}</li>
* </ul>
*
* @author Rod Johnson
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.0
* @deprecated as of Spring 3.0, in favor of using the listener-based test context framework
* ({@link org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests})
*/
@Deprecated
public abstract class AbstractAnnotationAwareTransactionalTests extends
AbstractTransactionalDataSourceSpringContextTests {
protected SimpleJdbcTemplate simpleJdbcTemplate;
private final TransactionAttributeSource transactionAttributeSource = new AnnotationTransactionAttributeSource();
/**
* {@link ProfileValueSource} available to subclasses but primarily intended
* for use in {@link #isDisabledInThisEnvironment(Method)}.
* <p>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 <code>name</code> 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
* <code>profileValueSource</code> 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 <code>null</code>
* @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() + " is 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 <code>testMethod</code> should
* run in the current environment.
* <p>The default implementation is based on
* {@link IfProfileValue @IfProfileValue} semantics.
* @param testMethod the test method
* @return <code>true</code> if the test is <em>disabled</em> 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 <em>rollback</em> 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;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2002-2010 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;
/**
* Test annotation which indicates that the
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* associated with a test is <em>dirty</em> and should be closed:
* <ul>
* <li>after the current test, when declared at the method level</li>
* <li>after each test method in the current test class, when declared at the
* class level with class mode set to {@link ClassMode#AFTER_EACH_TEST_METHOD
* AFTER_EACH_TEST_METHOD}</li>
* <li>after the current test class, when declared at the class level with class
* mode set to {@link ClassMode#AFTER_CLASS AFTER_CLASS}</li>
* </ul>
* <p>
* Use this annotation if a test has modified the context (for example, by
* replacing a bean definition). Subsequent tests will be supplied a new
* context.
* </p>
* <p>
* <code>&#064;DirtiesContext</code> may be used as a class-level and
* method-level annotation within the same class. In such scenarios, the
* <code>ApplicationContext</code> will be marked as <em>dirty</em> after any
* such annotated method as well as after the entire class. If the
* {@link ClassMode} is set to {@link ClassMode#AFTER_EACH_TEST_METHOD
* AFTER_EACH_TEST_METHOD}, the context will be marked dirty after each test
* method in the class.
* </p>
*
* @author Sam Brannen
* @author Rod Johnson
* @since 2.0
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DirtiesContext {
/**
* Defines <i>modes</i> which determine how <code>&#064;DirtiesContext</code>
* is interpreted when used to annotate a test class.
*/
static enum ClassMode {
/**
* The associated <code>ApplicationContext</code> will be marked as
* <em>dirty</em> after the test class.
*/
AFTER_CLASS,
/**
* The associated <code>ApplicationContext</code> will be marked as
* <em>dirty</em> after each test method in the class.
*/
AFTER_EACH_TEST_METHOD
}
/**
* The <i>mode</i> to use when a test class is annotated with
* <code>&#064;DirtiesContext</code>.
* <p>Defaults to {@link ClassMode#AFTER_CLASS AFTER_CLASS}.
* <p>Note: Setting the class mode on an annotated test method has no meaning,
* since the mere presence of the <code>&#064;DirtiesContext</code>
* annotation on a test method is sufficient.
*/
ClassMode classMode() default ClassMode.AFTER_CLASS;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2009 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 is required to throw the
* specified exception.
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
* @deprecated as of Spring 3.1 in favor of using built-in support for declaring
* expected exceptions in the underlying testing framework (e.g., JUnit, TestNG, etc.)
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Deprecated
public @interface ExpectedException {
Class<? extends Throwable> value();
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2009 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;
/**
* <p>
* 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.
* </p>
* <p>
* Note: <code>&#064;IfProfileValue</code> can be applied at the class level,
* the method level, or both. <code>&#064;IfProfileValue</code> at the class
* level overrides method-level usage of <code>&#064;IfProfileValue</code> for
* any methods within that class.
* </p>
* <p>
* 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:
* </p>
*
* <pre class="code">
* &#064;IfProfileValue(name = &quot;java.vendor&quot;, value = &quot;Sun Microsystems Inc.&quot;)
* public void testSomething() {
* // ...
* }
* </pre>
* <p>
* You can alternatively configure <code>&#064;IfProfileValue</code> with
* <em>OR</em> semantics for multiple {@link #values() values} as follows
* (assuming a {@link ProfileValueSource} has been appropriately configured for
* the &quot;test-groups&quot; name):
* </p>
*
* <pre class="code">
* &#064;IfProfileValue(name = &quot;test-groups&quot;, values = { &quot;unit-tests&quot;, &quot;integration-tests&quot; })
* public void testWhichRunsForUnitOrIntegrationTestGroups() {
* // ...
* }
* </pre>
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
* @see ProfileValueSource
* @see ProfileValueSourceConfiguration
* @see ProfileValueUtils
* @see org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests
* @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target( { ElementType.TYPE, ElementType.METHOD })
public @interface IfProfileValue {
/**
* The <code>name</code> of the <em>profile value</em> against which to
* test.
*/
String name();
/**
* A single, permissible <code>value</code> of the <em>profile value</em>
* for the given {@link #name() name}.
* <p>
* Note: Assigning values to both {@link #value()} and {@link #values()}
* will lead to a configuration conflict.
*/
String value() default "";
/**
* A list of all permissible <code>values</code> of the
* <em>profile value</em> for the given {@link #name() name}.
* <p>
* Note: Assigning values to both {@link #value()} and {@link #values()}
* will lead to a configuration conflict.
*/
String[] values() default {};
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2009 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 <i>not transactional</i>.
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
* @deprecated as of Spring 3.0, in favor of moving the non-transactional test
* method to a separate (non-transactional) test class or to a
* {@link org.springframework.test.context.transaction.BeforeTransaction
* &#64;BeforeTransaction} or
* {@link org.springframework.test.context.transaction.AfterTransaction
* &#64;AfterTransaction} method.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Deprecated
public @interface NotTransactional {
}

View File

@@ -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;
/**
* <p>
* Strategy interface for retrieving <em>profile values</em> for a given
* testing environment.
* </p>
* <p>
* Concrete implementations must provide a <code>public</code> no-args
* constructor.
* </p>
* <p>
* Spring provides the following out-of-the-box implementations:
* </p>
* <ul>
* <li>{@link SystemProfileValueSource}</li>
* </ul>
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
* @see ProfileValueSourceConfiguration
* @see IfProfileValue
* @see ProfileValueUtils
*/
public interface ProfileValueSource {
/**
* Get the <em>profile value</em> indicated by the specified key.
* @param key the name of the <em>profile value</em>
* @return the String value of the <em>profile value</em>, or <code>null</code>
* if there is no <em>profile value</em> with that key
*/
String get(String key);
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2009 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;
/**
* <p>
* ProfileValueSourceConfiguration is a class-level annotation which is used to
* specify what type of {@link ProfileValueSource} to use when retrieving
* <em>profile values</em> configured via the {@link IfProfileValue
* &#064;IfProfileValue} annotation.
* </p>
*
* @author Sam Brannen
* @since 2.5
* @see ProfileValueSource
* @see IfProfileValue
* @see ProfileValueUtils
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ProfileValueSourceConfiguration {
/**
* <p>
* The type of {@link ProfileValueSource} to use when retrieving
* <em>profile values</em>.
* </p>
*
* @see SystemProfileValueSource
*/
Class<? extends ProfileValueSource> value() default SystemProfileValueSource.class;
}

View File

@@ -0,0 +1,209 @@
/*
* Copyright 2002-2009 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 <em>profile values</em>.
*
* @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
* &#064;ProfileValueSourceConfiguration} annotation and instantiates a new
* instance of that type.
* <p>
* If {@link ProfileValueSourceConfiguration
* &#064;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<ProfileValueSourceConfiguration> annotationType = ProfileValueSourceConfiguration.class;
ProfileValueSourceConfiguration config = testClass.getAnnotation(annotationType);
if (logger.isDebugEnabled()) {
logger.debug("Retrieved @ProfileValueSourceConfiguration [" + config + "] for test class ["
+ testClass.getName() + "]");
}
Class<? extends ProfileValueSource> profileValueSourceType;
if (config != null) {
profileValueSourceType = config.value();
}
else {
profileValueSourceType = (Class<? extends ProfileValueSource>) AnnotationUtils.getDefaultValue(annotationType);
}
if (logger.isDebugEnabled()) {
logger.debug("Retrieved ProfileValueSource type [" + profileValueSourceType + "] for class ["
+ testClass.getName() + "]");
}
ProfileValueSource profileValueSource;
if (SystemProfileValueSource.class.equals(profileValueSourceType)) {
profileValueSource = SystemProfileValueSource.getInstance();
}
else {
try {
profileValueSource = profileValueSourceType.newInstance();
}
catch (Exception e) {
if (logger.isWarnEnabled()) {
logger.warn("Could not instantiate a ProfileValueSource of type [" + profileValueSourceType
+ "] for class [" + testClass.getName() + "]: using default.", e);
}
profileValueSource = SystemProfileValueSource.getInstance();
}
}
return profileValueSource;
}
/**
* Determine if the supplied <code>testClass</code> is <em>enabled</em> in
* the current environment, as specified by the {@link IfProfileValue
* &#064;IfProfileValue} annotation at the class level.
* <p>
* Defaults to <code>true</code> if no {@link IfProfileValue
* &#064;IfProfileValue} annotation is declared.
*
* @param testClass the test class
* @return <code>true</code> if the test is <em>enabled</em> in the current
* environment
*/
public static boolean isTestEnabledInThisEnvironment(Class<?> testClass) {
IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), ifProfileValue);
}
/**
* Determine if the supplied <code>testMethod</code> is <em>enabled</em> in
* the current environment, as specified by the {@link IfProfileValue
* &#064;IfProfileValue} annotation, which may be declared on the test
* method itself or at the class level. Class-level usage overrides
* method-level usage.
* <p>
* Defaults to <code>true</code> if no {@link IfProfileValue
* &#064;IfProfileValue} annotation is declared.
*
* @param testMethod the test method
* @param testClass the test class
* @return <code>true</code> if the test is <em>enabled</em> in the current
* environment
*/
public static boolean isTestEnabledInThisEnvironment(Method testMethod, Class<?> testClass) {
return isTestEnabledInThisEnvironment(retrieveProfileValueSource(testClass), testMethod, testClass);
}
/**
* Determine if the supplied <code>testMethod</code> is <em>enabled</em> in
* the current environment, as specified by the {@link IfProfileValue
* &#064;IfProfileValue} annotation, which may be declared on the test
* method itself or at the class level. Class-level usage overrides
* method-level usage.
* <p>
* Defaults to <code>true</code> if no {@link IfProfileValue
* &#064;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 <code>true</code> if the test is <em>enabled</em> in the current
* environment
*/
public static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, Method testMethod,
Class<?> testClass) {
IfProfileValue ifProfileValue = testClass.getAnnotation(IfProfileValue.class);
boolean classLevelEnabled = isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
if (classLevelEnabled) {
ifProfileValue = testMethod.getAnnotation(IfProfileValue.class);
return isTestEnabledInThisEnvironment(profileValueSource, ifProfileValue);
}
return false;
}
/**
* Determine if the <code>value</code> (or one of the <code>values</code>)
* in the supplied {@link IfProfileValue &#064;IfProfileValue} annotation is
* <em>enabled</em> in the current environment.
*
* @param profileValueSource the ProfileValueSource to use to determine if
* the test is enabled
* @param ifProfileValue the annotation to introspect; may be
* <code>null</code>
* @return <code>true</code> if the test is <em>enabled</em> in the current
* environment or if the supplied <code>ifProfileValue</code> is
* <code>null</code>
*/
private static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource,
IfProfileValue ifProfileValue) {
if (ifProfileValue == null) {
return true;
}
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;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2002-2009 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.
* <p />
* Note that the scope of execution to be repeated includes execution of the
* test method itself as well as any <em>set up</em> or <em>tear down</em> of
* the test fixture.
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Repeat {
int value() default 1;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2009 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 whether or not the transaction for the annotated
* test method should be <em>rolled back</em> after the test method has
* completed. If <code>true</code>, the transaction will be rolled back;
* otherwise, the transaction will be committed.
*
* @author Sam Brannen
* @since 2.5
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Rollback {
/**
* Whether or not the transaction for the annotated method should be rolled
* back after the method has completed.
*/
boolean value() default true;
}

View File

@@ -0,0 +1,58 @@
/*
* 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 org.springframework.util.Assert;
/**
* Implementation of {@link ProfileValueSource} which uses system properties as
* the underlying source.
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
*/
public class SystemProfileValueSource implements ProfileValueSource {
private static final SystemProfileValueSource INSTANCE = new SystemProfileValueSource();
/**
* Obtain the canonical instance of this ProfileValueSource.
*/
public static final SystemProfileValueSource getInstance() {
return INSTANCE;
}
/**
* Private constructor, enforcing the singleton pattern.
*/
private SystemProfileValueSource() {
}
/**
* Get the <em>profile value</em> indicated by the specified key from the
* system properties.
* @see System#getProperty(String)
*/
public String get(String key) {
Assert.hasText(key, "'key' must not be empty");
return System.getProperty(key);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2009 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;
/**
* <p>
* Test-specific annotation to indicate that a test method has to finish
* execution in a {@link #millis() specified time period}.
* </p>
* <p>
* If the text execution takes longer than the specified time period, then the
* test is to be considered failed.
* </p>
* <p>
* Note that the time period includes execution of the test method itself, any
* {@link Repeat repetitions} of the test, and any <em>set up</em> or
* <em>tear down</em> of the test fixture.
* </p>
*
* @author Rod Johnson
* @author Sam Brannen
* @since 2.0
* @see Repeat
* @see AbstractAnnotationAwareTransactionalTests
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Timed {
/**
* The maximum amount of time (in milliseconds) that a test execution can
* take without being marked as failed due to taking too long.
*/
long millis();
}

View File

@@ -0,0 +1,8 @@
/**
*
* Support classes for annotation-driven tests.
*
*/
package org.springframework.test.annotation;

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2002-2011 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.context;
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;
/**
* {@code ActiveProfiles} is a class-level annotation that is used to declare
* which <em>active bean definition profiles</em> should be used when loading
* an {@link org.springframework.context.ApplicationContext ApplicationContext}
* for test classes.
*
* @author Sam Brannen
* @since 3.1
* @see SmartContextLoader
* @see MergedContextConfiguration
* @see ContextConfiguration
* @see org.springframework.context.ApplicationContext
* @see org.springframework.context.annotation.Profile
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ActiveProfiles {
/**
* Alias for {@link #profiles}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction
* with {@link #profiles}, but it may be used <em>instead</em> of
* {@link #profiles}.
*/
String[] value() default {};
/**
* The bean definition profiles to activate.
*
* <p>This attribute may <strong>not</strong> be used in conjunction
* with {@link #value}, but it may be used <em>instead</em> of
* {@link #value}.
*/
String[] profiles() default {};
/**
* Whether or not bean definition profiles from superclasses should be
* <em>inherited</em>.
*
* <p>The default value is <code>true</code>, which means that an annotated
* class will <em>inherit</em> bean definition profiles defined by an
* annotated superclass. Specifically, the bean definition profiles for an
* annotated class will be appended to the list of bean definition profiles
* defined by an annotated superclass. Thus, subclasses have the option of
* <em>extending</em> the list of bean definition profiles.
*
* <p>If <code>inheritProfiles</code> is set to <code>false</code>, the bean
* definition profiles for the annotated class will <em>shadow</em> and
* effectively replace any bean definition profiles defined by a superclass.
*
* <p>In the following example, the {@code ApplicationContext} for
* {@code BaseTest} will be loaded using only the &quot;base&quot;
* bean definition profile; beans defined in the &quot;extended&quot; profile
* will therefore not be loaded. In contrast, the {@code ApplicationContext}
* for {@code ExtendedTest} will be loaded using the &quot;base&quot;
* <strong>and</strong> &quot;extended&quot; bean definition profiles.
* <pre class="code">
* &#064;ActiveProfiles(&quot;base&quot;)
* &#064;ContextConfiguration
* public class BaseTest {
* // ...
* }
*
* &#064;ActiveProfiles(&quot;extended&quot;)
* &#064;ContextConfiguration
* public class ExtendedTest extends BaseTest {
* // ...
* }
* </pre>
*
* <p>Note: {@code @ActiveProfiles} can be used when loading an
* {@code ApplicationContext} from path-based resource locations or
* configuration classes.
* @see ContextConfiguration#locations
* @see ContextConfiguration#classes
* @see ContextConfiguration#inheritLocations
*/
boolean inheritProfiles() default true;
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright 2002-2011 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.context;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* Cache for Spring {@link ApplicationContext ApplicationContexts}
* in a test environment.
*
* <p>Maintains a cache of {@link ApplicationContext contexts} keyed by
* {@link MergedContextConfiguration} instances. This has significant performance
* benefits if initializing the context would take time. While initializing a
* Spring context itself is very quick, some beans in a context, such as a
* {@link org.springframework.orm.hibernate3.LocalSessionFactoryBean LocalSessionFactoryBean}
* for working with Hibernate, may take some time to initialize. Hence it often
* makes sense to perform that initialization once.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
*/
class ContextCache {
/**
* Map of context keys to Spring ApplicationContext instances.
*/
private final Map<MergedContextConfiguration, ApplicationContext> contextMap = new ConcurrentHashMap<MergedContextConfiguration, ApplicationContext>();
private int hitCount;
private int missCount;
/**
* Clears all contexts from the cache.
*/
void clear() {
this.contextMap.clear();
}
/**
* Clears hit and miss count statistics for the cache (i.e., resets counters
* to zero).
*/
void clearStatistics() {
this.hitCount = 0;
this.missCount = 0;
}
/**
* Return whether there is a cached context for the given key.
* @param key the context key (never <code>null</code>)
*/
boolean contains(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
return this.contextMap.containsKey(key);
}
/**
* Obtain a cached ApplicationContext for the given key.
* <p>The {@link #getHitCount() hit} and {@link #getMissCount() miss}
* counts will be updated accordingly.
* @param key the context key (never <code>null</code>)
* @return the corresponding ApplicationContext instance,
* or <code>null</code> if not found in the cache.
* @see #remove
*/
ApplicationContext get(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
ApplicationContext context = this.contextMap.get(key);
if (context == null) {
incrementMissCount();
}
else {
incrementHitCount();
}
return context;
}
/**
* Increment the hit count by one. A <em>hit</em> is an access to the
* cache, which returned a non-null context for a queried key.
*/
private void incrementHitCount() {
this.hitCount++;
}
/**
* Increment the miss count by one. A <em>miss</em> is an access to the
* cache, which returned a <code>null</code> context for a queried key.
*/
private void incrementMissCount() {
this.missCount++;
}
/**
* Get the overall hit count for this cache. A <em>hit</em> is an access
* to the cache, which returned a non-null context for a queried key.
*/
int getHitCount() {
return this.hitCount;
}
/**
* Get the overall miss count for this cache. A <em>miss</em> is an
* access to the cache, which returned a <code>null</code> context for a
* queried key.
*/
int getMissCount() {
return this.missCount;
}
/**
* Explicitly add an ApplicationContext instance to the cache under the given key.
* @param key the context key (never <code>null</code>)
* @param context the ApplicationContext instance (never <code>null</code>)
*/
void put(MergedContextConfiguration key, ApplicationContext context) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(context, "ApplicationContext must not be null");
this.contextMap.put(key, context);
}
/**
* Remove the context with the given key.
* @param key the context key (never <code>null</code>)
* @return the corresponding ApplicationContext instance,
* or <code>null</code> if not found in the cache.
* @see #setDirty
*/
ApplicationContext remove(MergedContextConfiguration key) {
return this.contextMap.remove(key);
}
/**
* Mark the context with the given key as dirty, effectively
* {@link #remove removing} the context from the cache and explicitly
* {@link ConfigurableApplicationContext#close() closing} it if
* it is an instance of {@link ConfigurableApplicationContext}.
* <p>Generally speaking, you would only call this method if you change the
* state of a singleton bean, potentially affecting future interaction with
* the context.
* @param key the context key (never <code>null</code>)
* @see #remove
*/
void setDirty(MergedContextConfiguration key) {
Assert.notNull(key, "Key must not be null");
ApplicationContext context = remove(key);
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
}
}
/**
* Determine the number of contexts currently stored in the cache. If the
* cache contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*/
int size() {
return this.contextMap.size();
}
/**
* Generates a text string, which contains the {@link #size() size} as well
* as the {@link #hitCount hit} and {@link #missCount miss} counts.
*/
public String toString() {
return new ToStringCreator(this)//
.append("size", size())//
.append("hitCount", getHitCount())//
.append("missCount", getMissCount())//
.toString();
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2002-2011 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.context;
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;
/**
* {@code ContextConfiguration} defines class-level metadata that is
* used to determine how to load and configure an
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* for test classes.
*
* <p>Prior to Spring 3.1, only path-based resource locations were supported.
* As of Spring 3.1, {@link #loader context loaders} may choose to support
* either path-based or class-based resources (but not both). Consequently
* {@code @ContextConfiguration} can be used to declare either path-based
* resource locations (via the {@link #locations} or {@link #value}
* attribute) <i>or</i> configuration classes (via the {@link #classes}
* attribute).
*
* @author Sam Brannen
* @since 2.5
* @see ContextLoader
* @see SmartContextLoader
* @see ContextConfigurationAttributes
* @see MergedContextConfiguration
* @see org.springframework.context.ApplicationContext
* @see ActiveProfiles
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ContextConfiguration {
/**
* Alias for {@link #locations}.
*
* <p>This attribute may <strong>not</strong> be used in conjunction
* with {@link #locations} or {@link #classes}, but it may be used
* instead of {@link #locations}.
* @since 3.0
*/
String[] value() default {};
/**
* The resource locations to use for loading an
* {@link org.springframework.context.ApplicationContext ApplicationContext}.
*
* <p>Check out the Javadoc for
* {@link org.springframework.test.context.support.AbstractContextLoader#modifyLocations
* AbstractContextLoader.modifyLocations()} for details on how a location
* String will be interpreted at runtime, in particular in case of a relative
* path. Also, check out the documentation on
* {@link org.springframework.test.context.support.AbstractContextLoader#generateDefaultLocations
* AbstractContextLoader.generateDefaultLocations()} for details on the default
* locations that are going to be used if none are specified.
*
* <p>Note that the above-mentioned default rules only apply for a standard
* {@link org.springframework.test.context.support.AbstractContextLoader
* AbstractContextLoader} subclass such as
* {@link org.springframework.test.context.support.GenericXmlContextLoader
* GenericXmlContextLoader} which is the effective default implementation
* used at runtime if <code>locations</code> are configured. See the
* documentation for {@link #loader} for further details regarding default
* loaders.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #value} or {@link #classes}, but it may be used instead of
* {@link #value}.
* @since 2.5
*/
String[] locations() default {};
/**
* The {@link org.springframework.context.annotation.Configuration
* configuration classes} to use for loading an
* {@link org.springframework.context.ApplicationContext ApplicationContext}.
*
* <p>Check out the Javadoc for
* {@link org.springframework.test.context.support.AnnotationConfigContextLoader#detectDefaultConfigurationClasses
* AnnotationConfigContextLoader.detectDefaultConfigurationClasses()} for details
* on how default configuration classes will be detected if none are specified.
* See the documentation for {@link #loader} for further details regarding
* default loaders.
*
* <p>This attribute may <strong>not</strong> be used in conjunction with
* {@link #locations} or {@link #value}.
* @since 3.1
* @see org.springframework.context.annotation.Configuration
* @see org.springframework.test.context.support.AnnotationConfigContextLoader
*/
Class<?>[] classes() default {};
/**
* Whether or not {@link #locations resource locations} or
* {@link #classes configuration classes} from superclasses should be
* <em>inherited</em>.
*
* <p>The default value is <code>true</code>. This means that an annotated
* class will <em>inherit</em> the resource locations or configuration
* classes defined by annotated superclasses. Specifically, the resource
* locations or configuration classes for an annotated class will be
* appended to the list of resource locations or configuration classes
* defined by annotated superclasses. Thus, subclasses have the option of
* <em>extending</em> the list of resource locations or configuration
* classes.
*
* <p>If <code>inheritLocations</code> is set to <code>false</code>, the
* resource locations or configuration classes for the annotated class
* will <em>shadow</em> and effectively replace any resource locations
* or configuration classes defined by superclasses.
*
* <p>In the following example that uses path-based resource locations, the
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* for {@code ExtendedTest} will be loaded from
* &quot;base-context.xml&quot; <strong>and</strong>
* &quot;extended-context.xml&quot;, in that order. Beans defined in
* &quot;extended-context.xml&quot; may therefore override those defined in
* &quot;base-context.xml&quot;.
* <pre class="code">
* &#064;ContextConfiguration(&quot;base-context.xml&quot;)
* public class BaseTest {
* // ...
* }
*
* &#064;ContextConfiguration(&quot;extended-context.xml&quot;)
* public class ExtendedTest extends BaseTest {
* // ...
* }
* </pre>
*
* <p>Similarly, in the following example that uses configuration
* classes, the
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* for {@code ExtendedTest} will be loaded from the
* {@code BaseConfig} <strong>and</strong> {@code ExtendedConfig}
* configuration classes, in that order. Beans defined in
* {@code ExtendedConfig} may therefore override those defined in
* {@code BaseConfig}.
* <pre class="code">
* &#064;ContextConfiguration(classes=BaseConfig.class)
* public class BaseTest {
* // ...
* }
*
* &#064;ContextConfiguration(classes=ExtendedConfig.class)
* public class ExtendedTest extends BaseTest {
* // ...
* }
* </pre>
* @since 2.5
*/
boolean inheritLocations() default true;
/**
* The type of {@link ContextLoader} (or {@link SmartContextLoader}) to use
* for loading an {@link org.springframework.context.ApplicationContext
* ApplicationContext}.
*
* <p>If not specified, the loader will be inherited from the first superclass
* that is annotated with {@code @ContextConfiguration} and specifies an
* explicit loader. If no class in the hierarchy specifies an explicit
* loader, a default loader will be used instead.
*
* <p>The default concrete implementation chosen at runtime will be
* {@link org.springframework.test.context.support.DelegatingSmartContextLoader
* DelegatingSmartContextLoader}. For further details on the default behavior
* of various concrete {@code ContextLoaders}, check out the Javadoc for
* {@link org.springframework.test.context.support.AbstractContextLoader
* AbstractContextLoader},
* {@link org.springframework.test.context.support.GenericXmlContextLoader
* GenericXmlContextLoader}, and
* {@link org.springframework.test.context.support.AnnotationConfigContextLoader
* AnnotationConfigContextLoader}.
* @since 2.5
*/
Class<? extends ContextLoader> loader() default ContextLoader.class;
}

View File

@@ -0,0 +1,253 @@
/*
* Copyright 2002-2011 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.context;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* <code>ContextConfigurationAttributes</code> encapsulates the context
* configuration attributes declared on a test class via
* {@link ContextConfiguration @ContextConfiguration}.
*
* @author Sam Brannen
* @since 3.1
* @see ContextConfiguration
* @see SmartContextLoader#processContextConfiguration(ContextConfigurationAttributes)
* @see MergedContextConfiguration
*/
public class ContextConfigurationAttributes {
private static final Log logger = LogFactory.getLog(ContextConfigurationAttributes.class);
private final Class<?> declaringClass;
private String[] locations;
private Class<?>[] classes;
private final boolean inheritLocations;
private final Class<? extends ContextLoader> contextLoaderClass;
/**
* Resolve resource locations from the {@link ContextConfiguration#locations() locations}
* and {@link ContextConfiguration#value() value} attributes of the supplied
* {@link ContextConfiguration} annotation.
*
* @throws IllegalStateException if both the locations and value attributes have been declared
*/
private static String[] resolveLocations(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
Assert.notNull(declaringClass, "declaringClass must not be null");
String[] locations = contextConfiguration.locations();
String[] valueLocations = contextConfiguration.value();
if (!ObjectUtils.isEmpty(valueLocations) && !ObjectUtils.isEmpty(locations)) {
String msg = String.format("Test class [%s] has been configured with @ContextConfiguration's 'value' %s "
+ "and 'locations' %s attributes. Only one declaration of resource "
+ "locations is permitted per @ContextConfiguration annotation.", declaringClass.getName(),
ObjectUtils.nullSafeToString(valueLocations), ObjectUtils.nullSafeToString(locations));
logger.error(msg);
throw new IllegalStateException(msg);
}
else if (!ObjectUtils.isEmpty(valueLocations)) {
locations = valueLocations;
}
return locations;
}
/**
* Construct a new {@link ContextConfigurationAttributes} instance for the
* supplied {@link ContextConfiguration @ContextConfiguration} annotation and
* the {@link Class test class} that declared it.
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @param contextConfiguration the annotation from which to retrieve the attributes
*/
public ContextConfigurationAttributes(Class<?> declaringClass, ContextConfiguration contextConfiguration) {
this(declaringClass, resolveLocations(declaringClass, contextConfiguration), contextConfiguration.classes(),
contextConfiguration.inheritLocations(), contextConfiguration.loader());
}
/**
* Construct a new {@link ContextConfigurationAttributes} instance for the
* {@link Class test class} that declared the
* {@link ContextConfiguration @ContextConfiguration} annotation and its
* corresponding attributes.
*
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @param locations the resource locations declared via {@code @ContextConfiguration}
* @param classes the configuration classes declared via {@code @ContextConfiguration}
* @param inheritLocations the <code>inheritLocations</code> flag declared via {@code @ContextConfiguration}
* @param contextLoaderClass the {@code ContextLoader} class declared via {@code @ContextConfiguration}
* @throws IllegalArgumentException if the {@code declaringClass} or {@code contextLoaderClass} is
* <code>null</code>, or if the {@code locations} and {@code classes} are both non-empty
*/
public ContextConfigurationAttributes(Class<?> declaringClass, String[] locations, Class<?>[] classes,
boolean inheritLocations, Class<? extends ContextLoader> contextLoaderClass) {
Assert.notNull(declaringClass, "declaringClass must not be null");
Assert.notNull(contextLoaderClass, "contextLoaderClass must not be null");
if (!ObjectUtils.isEmpty(locations) && !ObjectUtils.isEmpty(classes)) {
String msg = String.format(
"Test class [%s] has been configured with @ContextConfiguration's 'locations' (or 'value') %s "
+ "and 'classes' %s attributes. Only one declaration of resources "
+ "is permitted per @ContextConfiguration annotation.", declaringClass.getName(),
ObjectUtils.nullSafeToString(locations), ObjectUtils.nullSafeToString(classes));
logger.error(msg);
throw new IllegalArgumentException(msg);
}
this.declaringClass = declaringClass;
this.locations = locations;
this.classes = classes;
this.inheritLocations = inheritLocations;
this.contextLoaderClass = contextLoaderClass;
}
/**
* Get the {@link Class class} that declared the
* {@link ContextConfiguration @ContextConfiguration} annotation.
* @return the declaring class; never <code>null</code>
*/
public Class<?> getDeclaringClass() {
return declaringClass;
}
/**
* Get the resource locations that were declared via
* {@link ContextConfiguration @ContextConfiguration}.
* <p>Note: this is a mutable property. The returned value may therefore
* represent a <em>processed</em> value that does not match the original value
* declared via {@link ContextConfiguration @ContextConfiguration}.
* @return the resource locations; potentially <code>null</code> or <em>empty</em>
* @see ContextConfiguration#value
* @see ContextConfiguration#locations
* @see #setLocations()
*/
public String[] getLocations() {
return locations;
}
/**
* Set the <em>processed</em> resource locations, effectively overriding the
* original value declared via {@link ContextConfiguration @ContextConfiguration}.
* @see #getLocations()
*/
public void setLocations(String[] locations) {
this.locations = locations;
}
/**
* Get the configuration classes that were declared via
* {@link ContextConfiguration @ContextConfiguration}.
* <p>Note: this is a mutable property. The returned value may therefore
* represent a <em>processed</em> value that does not match the original value
* declared via {@link ContextConfiguration @ContextConfiguration}.
* @return the configuration classes; potentially <code>null</code> or <em>empty</em>
* @see ContextConfiguration#classes
* @see #setClasses()
*/
public Class<?>[] getClasses() {
return classes;
}
/**
* Set the <em>processed</em> configuration classes, effectively overriding the
* original value declared via {@link ContextConfiguration @ContextConfiguration}.
* @see #getClasses()
*/
public void setClasses(Class<?>[] classes) {
this.classes = classes;
}
/**
* Determine if this {@code ContextConfigurationAttributes} instance has
* path-based resource locations.
* @return <code>true</code> if the {@link #getLocations() locations} array is not empty
* @see #hasResources()
* @see #hasClasses()
*/
public boolean hasLocations() {
return !ObjectUtils.isEmpty(getLocations());
}
/**
* Determine if this {@code ContextConfigurationAttributes} instance has
* class-based resources.
* @return <code>true</code> if the {@link #getClasses() classes} array is not empty
* @see #hasResources()
* @see #hasLocations()
*/
public boolean hasClasses() {
return !ObjectUtils.isEmpty(getClasses());
}
/**
* Determine if this {@code ContextConfigurationAttributes} instance has
* either path-based resource locations or class-based resources.
* @return <code>true</code> if either the {@link #getLocations() locations}
* or the {@link #getClasses() classes} array is not empty
* @see #hasLocations()
* @see #hasClasses()
*/
public boolean hasResources() {
return hasLocations() || hasClasses();
}
/**
* Get the <code>inheritLocations</code> flag that was declared via
* {@link ContextConfiguration @ContextConfiguration}.
* @return the <code>inheritLocations</code> flag
* @see ContextConfiguration#inheritLocations
*/
public boolean isInheritLocations() {
return inheritLocations;
}
/**
* Get the <code>ContextLoader</code> class that was declared via
* {@link ContextConfiguration @ContextConfiguration}.
* @return the <code>ContextLoader</code> class
* @see ContextConfiguration#loader
*/
public Class<? extends ContextLoader> getContextLoaderClass() {
return contextLoaderClass;
}
/**
* Provide a String representation of the context configuration attributes
* and declaring class.
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("declaringClass", declaringClass.getName())//
.append("locations", ObjectUtils.nullSafeToString(locations))//
.append("classes", ObjectUtils.nullSafeToString(classes))//
.append("inheritLocations", inheritLocations)//
.append("contextLoaderClass", contextLoaderClass.getName())//
.toString();
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2002-2011 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.context;
import org.springframework.context.ApplicationContext;
/**
* Strategy interface for loading an {@link ApplicationContext application context}
* for an integration test managed by the Spring TestContext Framework.
*
* <p><b>Note</b>: as of Spring 3.1, implement {@link SmartContextLoader} instead
* of this interface in order to provide support for configuration classes and
* active bean definition profiles.
*
* <p>Clients of a ContextLoader should call
* {@link #processLocations(Class,String...) processLocations()} prior to
* calling {@link #loadContext(String...) loadContext()} in case the
* ContextLoader provides custom support for modifying or generating locations.
* The results of {@link #processLocations(Class,String...) processLocations()}
* should then be supplied to {@link #loadContext(String...) loadContext()}.
*
* <p>Concrete implementations must provide a <code>public</code> no-args
* constructor.
*
* <p>Spring provides the following out-of-the-box implementations:
* <ul>
* <li>{@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader}</li>
* <li>{@link org.springframework.test.context.support.GenericPropertiesContextLoader GenericPropertiesContextLoader}</li>
* </ul>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see SmartContextLoader
* @see org.springframework.test.context.support.AnnotationConfigContextLoader AnnotationConfigContextLoader
*/
public interface ContextLoader {
/**
* Processes application context resource locations for a specified class.
* <p>Concrete implementations may choose to modify the supplied locations,
* generate new locations, or simply return the supplied locations unchanged.
* @param clazz the class with which the locations are associated: used to
* determine how to process the supplied locations
* @param locations the unmodified locations to use for loading the
* application context (can be <code>null</code> or empty)
* @return an array of application context resource locations
*/
String[] processLocations(Class<?> clazz, String... locations);
/**
* Loads a new {@link ApplicationContext context} based on the supplied
* <code>locations</code>, configures the context, and finally returns
* the context in fully <em>refreshed</em> state.
* <p>Configuration locations are generally considered to be classpath
* resources by default.
* <p>Concrete implementations should register annotation configuration
* processors with bean factories of {@link ApplicationContext application
* contexts} loaded by this ContextLoader. Beans will therefore automatically
* be candidates for annotation-based dependency injection using
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired},
* {@link javax.annotation.Resource @Resource}, and
* {@link javax.inject.Inject @Inject}.
* <p>Any ApplicationContext loaded by a ContextLoader <strong>must</strong>
* 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.
* @param locations the resource locations to use to load the application context
* @return a new application context
* @throws Exception if context loading failed
*/
ApplicationContext loadContext(String... locations) throws Exception;
}

View File

@@ -0,0 +1,330 @@
/*
* Copyright 2002-2011 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.context;
import static org.springframework.beans.BeanUtils.instantiateClass;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotationDeclaringClass;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Utility methods for working with {@link ContextLoader ContextLoaders} and
* {@link SmartContextLoader SmartContextLoaders} and resolving resource locations,
* configuration classes, and active bean definition profiles.
*
* @author Sam Brannen
* @since 3.1
* @see ContextLoader
* @see SmartContextLoader
* @see ContextConfiguration
* @see ContextConfigurationAttributes
* @see ActiveProfiles
* @see MergedContextConfiguration
*/
abstract class ContextLoaderUtils {
private static final Log logger = LogFactory.getLog(ContextLoaderUtils.class);
private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader";
private ContextLoaderUtils() {
/* no-op */
}
/**
* Resolve the {@link ContextLoader} {@link Class class} to use for the
* supplied {@link Class testClass} and then instantiate and return that
* {@code ContextLoader}.
* <p>If the supplied <code>defaultContextLoaderClassName</code> is
* <code>null</code> or <em>empty</em>, the <em>standard</em>
* default context loader class name {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
* will be used. For details on the class resolution process, see
* {@link #resolveContextLoaderClass()}.
* @param testClass the test class for which the {@code ContextLoader}
* should be resolved (must not be <code>null</code>)
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use (may be <code>null</code>)
* @return the resolved {@code ContextLoader} for the supplied
* <code>testClass</code> (never <code>null</code>)
* @see #resolveContextLoaderClass()
*/
static ContextLoader resolveContextLoader(Class<?> testClass, String defaultContextLoaderClassName) {
Assert.notNull(testClass, "Test class must not be null");
if (!StringUtils.hasText(defaultContextLoaderClassName)) {
defaultContextLoaderClassName = DEFAULT_CONTEXT_LOADER_CLASS_NAME;
}
Class<? extends ContextLoader> contextLoaderClass = resolveContextLoaderClass(testClass,
defaultContextLoaderClassName);
return instantiateClass(contextLoaderClass, ContextLoader.class);
}
/**
* Resolve the {@link ContextLoader} {@link Class} to use for the supplied
* {@link Class testClass}.
* <ol>
* <li>If the {@link ContextConfiguration#loader() loader} attribute of
* {@link ContextConfiguration &#064;ContextConfiguration} is configured
* with an explicit class, that class will be returned.</li>
* <li>If a <code>loader</code> class is not specified, the class hierarchy
* will be traversed to find a parent class annotated with
* {@code @ContextConfiguration}; go to step #1.</li>
* <li>If no explicit <code>loader</code> class is found after traversing
* the class hierarchy, an attempt will be made to load and return the class
* with the supplied <code>defaultContextLoaderClassName</code>.</li>
* </ol>
* @param testClass the class for which to resolve the {@code ContextLoader}
* class; must not be <code>null</code>
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use; must not be <code>null</code> or empty
* @return the {@code ContextLoader} class to use for the supplied test class
* @throws IllegalArgumentException if {@code @ContextConfiguration} is not
* <em>present</em> on the supplied test class
* @throws IllegalStateException if the default {@code ContextLoader} class
* could not be loaded
*/
@SuppressWarnings("unchecked")
static Class<? extends ContextLoader> resolveContextLoaderClass(Class<?> testClass,
String defaultContextLoaderClassName) {
Assert.notNull(testClass, "Class must not be null");
Assert.hasText(defaultContextLoaderClassName, "Default ContextLoader class name must not be null or empty");
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, testClass);
Assert.notNull(declaringClass, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and test class [%s]",
annotationType, testClass));
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace(String.format(
"Processing ContextLoader for @ContextConfiguration [%s] and declaring class [%s]",
contextConfiguration, declaringClass));
}
Class<? extends ContextLoader> contextLoaderClass = contextConfiguration.loader();
if (!ContextLoader.class.equals(contextLoaderClass)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Found explicit ContextLoader class [%s] for @ContextConfiguration [%s] and declaring class [%s]",
contextLoaderClass, contextConfiguration, declaringClass));
}
return contextLoaderClass;
}
declaringClass = findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass());
}
try {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Using default ContextLoader class [%s] for test class [%s]",
defaultContextLoaderClassName, testClass));
}
return (Class<? extends ContextLoader>) ContextLoaderUtils.class.getClassLoader().loadClass(
defaultContextLoaderClassName);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Could not load default ContextLoader class ["
+ defaultContextLoaderClassName + "]. Specify @ContextConfiguration's 'loader' "
+ "attribute or make the default loader class available.");
}
}
/**
* Resolve the list of {@link ContextConfigurationAttributes configuration
* attributes} for the supplied {@link Class class} and its superclasses.
* <p>Note that the {@link ContextConfiguration#inheritLocations
* inheritLocations} flag of {@link ContextConfiguration
* &#064;ContextConfiguration} will be taken into consideration.
* Specifically, if the <code>inheritLocations</code> flag is set to
* <code>true</code>, configuration attributes defined in the annotated
* class will be appended to the configuration attributes defined in
* superclasses.
* @param clazz the class for which to resolve the configuration attributes (must
* not be <code>null</code>)
* @return the list of configuration attributes for the specified class,
* including configuration attributes from superclasses if appropriate
* (never <code>null</code>)
* @throws IllegalArgumentException if the supplied class is <code>null</code> or
* if {@code @ContextConfiguration} is not <em>present</em> on the supplied class
*/
static List<ContextConfigurationAttributes> resolveContextConfigurationAttributes(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
final List<ContextConfigurationAttributes> attributesList = new ArrayList<ContextConfigurationAttributes>();
Class<ContextConfiguration> annotationType = ContextConfiguration.class;
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, clazz);
Assert.notNull(declaringClass, String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", annotationType,
clazz));
while (declaringClass != null) {
ContextConfiguration contextConfiguration = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for declaring class [%s].",
contextConfiguration, declaringClass));
}
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(declaringClass,
contextConfiguration);
if (logger.isTraceEnabled()) {
logger.trace("Resolved context configuration attributes: " + attributes);
}
attributesList.add(0, attributes);
declaringClass = contextConfiguration.inheritLocations() ? findAnnotationDeclaringClass(annotationType,
declaringClass.getSuperclass()) : null;
}
return attributesList;
}
/**
* Resolve <em>active bean definition profiles</em> for the supplied {@link Class}.
* <p>Note that the {@link ActiveProfiles#inheritProfiles inheritProfiles}
* flag of {@link ActiveProfiles &#064;ActiveProfiles} will be taken into
* consideration. Specifically, if the <code>inheritProfiles</code> flag is
* set to <code>true</code>, profiles defined in the annotated class will be
* merged with those defined in superclasses.
* @param clazz the class for which to resolve the active profiles (must
* not be <code>null</code>)
* @return the set of active profiles for the specified class, including
* active profiles from superclasses if appropriate (never <code>null</code>)
* @see org.springframework.test.context.ActiveProfiles
* @see org.springframework.context.annotation.Profile
*/
static String[] resolveActiveProfiles(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
Class<?> declaringClass = findAnnotationDeclaringClass(annotationType, clazz);
if (declaringClass == null && logger.isDebugEnabled()) {
logger.debug(String.format(
"Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]",
annotationType, clazz));
}
final Set<String> activeProfiles = new HashSet<String>();
while (declaringClass != null) {
ActiveProfiles annotation = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation,
declaringClass));
}
String[] profiles = annotation.profiles();
String[] valueProfiles = annotation.value();
if (!ObjectUtils.isEmpty(valueProfiles) && !ObjectUtils.isEmpty(profiles)) {
String msg = String.format("Test class [%s] has been configured with @ActiveProfiles' 'value' [%s] "
+ "and 'profiles' [%s] attributes. Only one declaration of active bean "
+ "definition profiles is permitted per @ActiveProfiles annotation.", declaringClass,
ObjectUtils.nullSafeToString(valueProfiles), ObjectUtils.nullSafeToString(profiles));
logger.error(msg);
throw new IllegalStateException(msg);
}
else if (!ObjectUtils.isEmpty(valueProfiles)) {
profiles = valueProfiles;
}
for (String profile : profiles) {
if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim());
}
}
declaringClass = annotation.inheritProfiles() ? findAnnotationDeclaringClass(annotationType,
declaringClass.getSuperclass()) : null;
}
return StringUtils.toStringArray(activeProfiles);
}
/**
* Build the {@link MergedContextConfiguration merged context configuration}
* for the supplied {@link Class testClass} and
* <code>defaultContextLoaderClassName</code>.
* @param testClass the test class for which the {@code MergedContextConfiguration}
* should be built (must not be <code>null</code>)
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use (may be <code>null</code>)
* @return the merged context configuration
* @see #resolveContextLoader()
* @see #resolveContextConfigurationAttributes()
* @see SmartContextLoader#processContextConfiguration()
* @see ContextLoader#processLocations()
* @see #resolveActiveProfiles()
* @see MergedContextConfiguration
*/
static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testClass,
String defaultContextLoaderClassName) {
final ContextLoader contextLoader = resolveContextLoader(testClass, defaultContextLoaderClassName);
final List<ContextConfigurationAttributes> configAttributesList = resolveContextConfigurationAttributes(testClass);
final List<String> locationsList = new ArrayList<String>();
final List<Class<?>> classesList = new ArrayList<Class<?>>();
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
if (logger.isTraceEnabled()) {
logger.trace(String.format(
"Processing locations and classes for context configuration attributes [%s]", configAttributes));
}
if (contextLoader instanceof SmartContextLoader) {
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
smartContextLoader.processContextConfiguration(configAttributes);
locationsList.addAll(Arrays.asList(configAttributes.getLocations()));
classesList.addAll(Arrays.asList(configAttributes.getClasses()));
}
else {
String[] processedLocations = contextLoader.processLocations(configAttributes.getDeclaringClass(),
configAttributes.getLocations());
locationsList.addAll(Arrays.asList(processedLocations));
// Legacy ContextLoaders don't know how to process classes
}
}
String[] locations = StringUtils.toStringArray(locationsList);
Class<?>[] classes = ClassUtils.toClassArray(classesList);
String[] activeProfiles = resolveActiveProfiles(testClass);
return new MergedContextConfiguration(testClass, locations, classes, activeProfiles, contextLoader);
}
}

View File

@@ -0,0 +1,230 @@
/*
* Copyright 2002-2011 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.context;
import java.io.Serializable;
import java.util.Arrays;
import java.util.SortedSet;
import java.util.TreeSet;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* {@code MergedContextConfiguration} encapsulates the <em>merged</em>
* context configuration declared on a test class and all of its superclasses
* via {@link ContextConfiguration @ContextConfiguration} and
* {@link ActiveProfiles @ActiveProfiles}.
*
* <p>Merged resource locations, configuration classes, and active profiles
* represent all declared values in the test class hierarchy taking into
* consideration the semantics of the
* {@link ContextConfiguration#inheritLocations inheritLocations} and
* {@link ActiveProfiles#inheritProfiles inheritProfiles} flags in
* {@code @ContextConfiguration} and {@code @ActiveProfiles}, respectively.
*
* <p>A {@link SmartContextLoader} uses {@code MergedContextConfiguration}
* to load an {@link org.springframework.context.ApplicationContext ApplicationContext}.
*
* <p>{@code MergedContextConfiguration} is also used by the {@link TestContext}
* as the context cache key for caching an
* {@link org.springframework.context.ApplicationContext ApplicationContext}
* that was loaded using properties of this {@code MergedContextConfiguration}.
*
* @author Sam Brannen
* @since 3.1
* @see ContextConfiguration
* @see ActiveProfiles
* @see ContextConfigurationAttributes
* @see SmartContextLoader#loadContext(MergedContextConfiguration)
*/
public class MergedContextConfiguration implements Serializable {
private static final long serialVersionUID = -3290560718464957422L;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
private final Class<?> testClass;
private final String[] locations;
private final Class<?>[] classes;
private final String[] activeProfiles;
private final ContextLoader contextLoader;
private static String[] processLocations(String[] locations) {
return locations == null ? EMPTY_STRING_ARRAY : locations;
}
private static Class<?>[] processClasses(Class<?>[] classes) {
return classes == null ? EMPTY_CLASS_ARRAY : classes;
}
private static String[] processActiveProfiles(String[] activeProfiles) {
if (activeProfiles == null) {
return EMPTY_STRING_ARRAY;
}
// Active profiles must be unique and sorted in order to support proper
// cache key generation. Specifically, profile sets {foo,bar} and
// {bar,foo} must both result in the same array (e.g., [bar,foo]).
SortedSet<String> sortedProfilesSet = new TreeSet<String>(Arrays.asList(activeProfiles));
return StringUtils.toStringArray(sortedProfilesSet);
}
/**
* Generate a null-safe {@link String} representation of the supplied
* {@link ContextLoader} based solely on the fully qualified name of the
* loader or &quot;null&quot; if the supplied loaded is <code>null</code>.
*/
private static String nullSafeToString(ContextLoader contextLoader) {
return contextLoader == null ? "null" : contextLoader.getClass().getName();
}
/**
* Create a new {@code MergedContextConfiguration} instance for the
* supplied {@link Class test class}, resource locations, configuration
* classes, active profiles, and {@link ContextLoader}.
* <p>If a <code>null</code> value is supplied for <code>locations</code>,
* <code>classes</code>, or <code>activeProfiles</code> an empty array will
* be stored instead. Furthermore, active profiles will be sorted, and duplicate
* profiles will be removed.
* @param testClass the test class for which the configuration was merged
* @param locations the merged resource locations
* @param classes the merged configuration classes
* @param activeProfiles the merged active bean definition profiles
* @param contextLoader the resolved <code>ContextLoader</code>
*/
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
String[] activeProfiles, ContextLoader contextLoader) {
this.testClass = testClass;
this.locations = processLocations(locations);
this.classes = processClasses(classes);
this.activeProfiles = processActiveProfiles(activeProfiles);
this.contextLoader = contextLoader;
}
/**
* Get the {@link Class test class} associated with this
* {@code MergedContextConfiguration}.
*/
public Class<?> getTestClass() {
return testClass;
}
/**
* Get the merged resource locations for the
* {@link #getTestClass() test class}.
*/
public String[] getLocations() {
return locations;
}
/**
* Get the merged configuration classes for the
* {@link #getTestClass() test class}.
*/
public Class<?>[] getClasses() {
return classes;
}
/**
* Get the merged active bean definition profiles for the
* {@link #getTestClass() test class}.
*/
public String[] getActiveProfiles() {
return activeProfiles;
}
/**
* Get the resolved {@link ContextLoader} for the
* {@link #getTestClass() test class}.
*/
public ContextLoader getContextLoader() {
return contextLoader;
}
/**
* Generate a unique hash code for all properties of this
* {@code MergedContextConfiguration} excluding the
* {@link #getTestClass() test class}.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(locations);
result = prime * result + Arrays.hashCode(classes);
result = prime * result + Arrays.hashCode(activeProfiles);
result = prime * result + nullSafeToString(contextLoader).hashCode();
return result;
}
/**
* Determine if the supplied object is equal to this {@code MergedContextConfiguration}
* instance by comparing both object's {@link #getLocations() locations},
* {@link #getClasses() configuration classes}, {@link #getActiveProfiles()
* active profiles}, and the fully qualified names of their
* {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MergedContextConfiguration)) {
return false;
}
final MergedContextConfiguration that = (MergedContextConfiguration) obj;
if (!Arrays.equals(this.locations, that.locations)) {
return false;
}
if (!Arrays.equals(this.classes, that.classes)) {
return false;
}
if (!Arrays.equals(this.activeProfiles, that.activeProfiles)) {
return false;
}
if (!nullSafeToString(this.contextLoader).equals(nullSafeToString(that.contextLoader))) {
return false;
}
return true;
}
/**
* Provide a String representation of the {@link #getTestClass() test class},
* {@link #getLocations() locations}, {@link #getClasses() configuration classes},
* {@link #getActiveProfiles() active profiles}, and the name of the
* {@link #getContextLoader() ContextLoader}.
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("testClass", testClass)//
.append("locations", ObjectUtils.nullSafeToString(locations))//
.append("classes", ObjectUtils.nullSafeToString(classes))//
.append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))//
.append("contextLoader", nullSafeToString(contextLoader))//
.toString();
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2011 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.context;
import org.springframework.context.ApplicationContext;
/**
* Strategy interface for loading an {@link ApplicationContext application context}
* for an integration test managed by the Spring TestContext Framework.
*
* <p>The {@code SmartContextLoader} SPI supersedes the {@link ContextLoader} SPI
* introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process
* either resource locations or configuration classes. Furthermore, a
* {@code SmartContextLoader} can set active bean definition profiles in the
* context that it loads (see {@link MergedContextConfiguration#getActiveProfiles()}
* and {@link #loadContext(MergedContextConfiguration)}).
*
* <p>Clients of a {@code SmartContextLoader} should call
* {@link #processContextConfiguration(ContextConfigurationAttributes)
* processContextConfiguration()} prior to calling
* {@link #loadContext(MergedContextConfiguration) loadContext()}. This gives a
* {@code SmartContextLoader} the opportunity to provide custom support for
* modifying resource locations or detecting default resource locations or
* default configuration classes. The results of
* {@link #processContextConfiguration(ContextConfigurationAttributes)
* processContextConfiguration()} should be merged for all classes in the
* hierarchy of the root test class and then supplied to
* {@link #loadContext(MergedContextConfiguration) loadContext()}.
*
* <p>Even though {@code SmartContextLoader} extends {@code ContextLoader},
* clients should favor {@code SmartContextLoader}-specific methods over those
* defined in {@code ContextLoader}, particularly because a
* {@code SmartContextLoader} may choose not to support methods defined in
* the {@code ContextLoader} SPI.
*
* <p>Concrete implementations must provide a <code>public</code> no-args constructor.
*
* <p>Spring provides the following out-of-the-box implementations:
* <ul>
* <li>{@link org.springframework.test.context.support.DelegatingSmartContextLoader DelegatingSmartContextLoader}</li>
* <li>{@link org.springframework.test.context.support.AnnotationConfigContextLoader AnnotationConfigContextLoader}</li>
* <li>{@link org.springframework.test.context.support.GenericXmlContextLoader GenericXmlContextLoader}</li>
* <li>{@link org.springframework.test.context.support.GenericPropertiesContextLoader GenericPropertiesContextLoader}</li>
* </ul>
*
* @author Sam Brannen
* @since 3.1
* @see ContextConfiguration
* @see ActiveProfiles
* @see ContextConfigurationAttributes
* @see MergedContextConfiguration
*/
public interface SmartContextLoader extends ContextLoader {
/**
* Processes the {@link ContextConfigurationAttributes} for a given test class.
* <p>Concrete implementations may choose to <em>modify</em> the <code>locations</code>
* or <code>classes</code> in the supplied {@link ContextConfigurationAttributes},
* <em>generate</em> default configuration locations, or <em>detect</em>
* default configuration classes if the supplied values are <code>null</code>
* or empty.
* <p><b>Note</b>: in contrast to a standard {@code ContextLoader}, a
* {@code SmartContextLoader} <b>must</b> <em>preemptively</em> verify that
* a generated or detected default actually exists before setting the corresponding
* <code>locations</code> or <code>classes</code> property in the supplied
* {@link ContextConfigurationAttributes}. Consequently, leaving the
* <code>locations</code> or <code>classes</code> property empty signals that
* this {@code SmartContextLoader} was not able to generate or detect defaults.
* @param configAttributes the context configuration attributes to process
*/
void processContextConfiguration(ContextConfigurationAttributes configAttributes);
/**
* Loads a new {@link ApplicationContext context} based on the supplied
* {@link MergedContextConfiguration merged context configuration},
* configures the context, and finally returns the context in a fully
* <em>refreshed</em> state.
* <p>Concrete implementations should register annotation configuration
* processors with bean factories of
* {@link ApplicationContext application contexts} loaded by this
* {@code SmartContextLoader}. Beans will therefore automatically be
* candidates for annotation-based dependency injection using
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired},
* {@link javax.annotation.Resource @Resource}, and
* {@link javax.inject.Inject @Inject}. In addition, concrete implementations
* should set the active bean definition profiles in the context's
* {@link org.springframework.core.env.Environment Environment}.
* <p>Any <code>ApplicationContext</code> loaded by a
* {@code SmartContextLoader} <strong>must</strong> 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 of external resources held by beans within the context (e.g.,
* temporary files).
* @param mergedConfig the merged context configuration to use to load the
* application context
* @return a new application context
* @throws Exception if context loading failed
* @see #processContextConfiguration(ContextConfigurationAttributes)
* @see org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors()
* @see org.springframework.test.context.MergedContextConfiguration#getActiveProfiles()
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
*/
ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception;
}

View File

@@ -0,0 +1,251 @@
/*
* Copyright 2002-2011 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.context;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.AttributeAccessorSupport;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
/**
* <code>TestContext</code> encapsulates the context in which a test is executed,
* agnostic of the actual testing framework in use.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
*/
public class TestContext extends AttributeAccessorSupport {
private static final long serialVersionUID = -5827157174866681233L;
private static final Log logger = LogFactory.getLog(TestContext.class);
private final ContextCache contextCache;
private final MergedContextConfiguration mergedContextConfiguration;
private final Class<?> testClass;
private Object testInstance;
private Method testMethod;
private Throwable testException;
/**
* Delegates to {@link #TestContext(Class, ContextCache, String)} with a
* value of <code>null</code> for the default {@code ContextLoader} class name.
*/
TestContext(Class<?> testClass, ContextCache contextCache) {
this(testClass, contextCache, null);
}
/**
* Construct a new test context for the supplied {@link Class test class}
* and {@link ContextCache context cache} and parse the corresponding
* {@link ContextConfiguration &#064;ContextConfiguration} annotation, if
* present.
* <p>If the supplied class name for the default {@code ContextLoader}
* is <code>null</code> or <em>empty</em> and no concrete {@code ContextLoader}
* class is explicitly supplied via the {@code @ContextConfiguration}
* annotation, a
* {@link org.springframework.test.context.support.DelegatingSmartContextLoader
* DelegatingSmartContextLoader} will be used instead.
* @param testClass the test class for which the test context should be
* constructed (must not be <code>null</code>)
* @param contextCache the context cache from which the constructed test
* context should retrieve application contexts (must not be
* <code>null</code>)
* @param defaultContextLoaderClassName the name of the default
* {@code ContextLoader} class to use (may be <code>null</code>)
*/
TestContext(Class<?> testClass, ContextCache contextCache, String defaultContextLoaderClassName) {
Assert.notNull(testClass, "Test class must not be null");
Assert.notNull(contextCache, "ContextCache must not be null");
MergedContextConfiguration mergedContextConfiguration;
ContextConfiguration contextConfiguration = testClass.getAnnotation(ContextConfiguration.class);
if (contextConfiguration == null) {
if (logger.isInfoEnabled()) {
logger.info(String.format("@ContextConfiguration not found for class [%s]", testClass));
}
mergedContextConfiguration = new MergedContextConfiguration(testClass, null, null, null, null);
}
else {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Retrieved @ContextConfiguration [%s] for class [%s]", contextConfiguration,
testClass));
}
mergedContextConfiguration = ContextLoaderUtils.buildMergedContextConfiguration(testClass,
defaultContextLoaderClassName);
}
this.contextCache = contextCache;
this.mergedContextConfiguration = mergedContextConfiguration;
this.testClass = testClass;
}
/**
* Load an <code>ApplicationContext</code> for this test context using the
* configured {@code ContextLoader} and merged context configuration. Supports
* both the {@link SmartContextLoader} and {@link ContextLoader} SPIs.
* @throws Exception if an error occurs while loading the application context
*/
private ApplicationContext loadApplicationContext() throws Exception {
ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
Assert.notNull(contextLoader, "Can not load an ApplicationContext with a NULL 'contextLoader'. "
+ "Consider annotating your test class with @ContextConfiguration.");
ApplicationContext applicationContext;
if (contextLoader instanceof SmartContextLoader) {
SmartContextLoader smartContextLoader = (SmartContextLoader) contextLoader;
applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
}
else {
String[] locations = mergedContextConfiguration.getLocations();
Assert.notNull(locations, "Can not load an ApplicationContext with a NULL 'locations' array. "
+ "Consider annotating your test class with @ContextConfiguration.");
applicationContext = contextLoader.loadContext(locations);
}
return applicationContext;
}
/**
* Get the {@link ApplicationContext application context} for this test
* context, possibly cached.
* @return the application context
* @throws IllegalStateException if an error occurs while retrieving the
* application context
*/
public ApplicationContext getApplicationContext() {
synchronized (contextCache) {
ApplicationContext context = contextCache.get(mergedContextConfiguration);
if (context == null) {
try {
context = loadApplicationContext();
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Storing ApplicationContext for test class [%s] in cache under key [%s].", testClass,
mergedContextConfiguration));
}
contextCache.put(mergedContextConfiguration, context);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to load ApplicationContext", ex);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
"Retrieved ApplicationContext for test class [%s] from cache with key [%s].", testClass,
mergedContextConfiguration));
}
}
return context;
}
}
/**
* Get the {@link Class test class} for this test context.
* @return the test class (never <code>null</code>)
*/
public final Class<?> getTestClass() {
return testClass;
}
/**
* Get the current {@link Object test instance} for this test context.
* <p>Note: this is a mutable property.
* @return the current test instance (may be <code>null</code>)
* @see #updateState(Object,Method,Throwable)
*/
public final Object getTestInstance() {
return testInstance;
}
/**
* Get the current {@link Method test method} for this test context.
* <p>Note: this is a mutable property.
* @return the current test method (may be <code>null</code>)
* @see #updateState(Object, Method, Throwable)
*/
public final Method getTestMethod() {
return testMethod;
}
/**
* Get the {@link Throwable exception} that was thrown during execution of
* the {@link #getTestMethod() test method}.
* <p>Note: this is a mutable property.
* @return the exception that was thrown, or <code>null</code> if no
* exception was thrown
* @see #updateState(Object, Method, Throwable)
*/
public final Throwable getTestException() {
return testException;
}
/**
* Call this method to signal that the {@link ApplicationContext application
* context} associated with this test context is <em>dirty</em> and should
* be reloaded. Do this if a test has modified the context (for example, by
* replacing a bean definition).
*/
public void markApplicationContextDirty() {
synchronized (contextCache) {
contextCache.setDirty(mergedContextConfiguration);
}
}
/**
* Update this test context to reflect the state of the currently executing
* test.
* @param testInstance the current test instance (may be <code>null</code>)
* @param testMethod the current test method (may be <code>null</code>)
* @param testException the exception that was thrown in the test method, or
* <code>null</code> if no exception was thrown
*/
void updateState(Object testInstance, Method testMethod, Throwable testException) {
this.testInstance = testInstance;
this.testMethod = testMethod;
this.testException = testException;
}
/**
* Provide a String representation of this test context's state.
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("testClass", testClass)//
.append("testInstance", testInstance)//
.append("testMethod", testMethod)//
.append("testException", testException)//
.append("mergedContextConfiguration", mergedContextConfiguration)//
.toString();
}
}

View File

@@ -0,0 +1,463 @@
/*
* Copyright 2002-2010 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.context;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* <p>
* <code>TestContextManager</code> is the main entry point into the
* <em>Spring TestContext Framework</em>, which provides support for loading and
* accessing {@link ApplicationContext application contexts}, dependency
* injection of test instances,
* {@link org.springframework.transaction.annotation.Transactional
* transactional} execution of test methods, etc.
* </p>
* <p>
* Specifically, a <code>TestContextManager</code> is responsible for managing a
* single {@link TestContext} and signaling events to all registered
* {@link TestExecutionListener TestExecutionListeners} at well defined test
* execution points:
* </p>
* <ul>
* <li>{@link #beforeTestClass() before test class execution}: prior to any
* <em>before class methods</em> of a particular testing framework (e.g., JUnit
* 4's {@link org.junit.BeforeClass &#064;BeforeClass})</li>
* <li>{@link #prepareTestInstance(Object) test instance preparation}:
* immediately following instantiation of the test instance</li>
* <li>{@link #beforeTestMethod(Object,Method) before test method execution}:
* prior to any <em>before methods</em> of a particular testing framework (e.g.,
* JUnit 4's {@link org.junit.Before &#064;Before})</li>
* <li>{@link #afterTestMethod(Object,Method,Throwable) after test method
* execution}: after any <em>after methods</em> of a particular testing
* framework (e.g., JUnit 4's {@link org.junit.After &#064;After})</li>
* <li>{@link #afterTestClass() after test class execution}: after any
* <em>after class methods</em> of a particular testing framework (e.g., JUnit
* 4's {@link org.junit.AfterClass &#064;AfterClass})</li>
* </ul>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see TestContext
* @see TestExecutionListeners
* @see ContextConfiguration
* @see org.springframework.test.context.transaction.TransactionConfiguration
*/
public class TestContextManager {
private static final String[] DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES = new String[] {
"org.springframework.test.context.support.DependencyInjectionTestExecutionListener",
"org.springframework.test.context.support.DirtiesContextTestExecutionListener",
"org.springframework.test.context.transaction.TransactionalTestExecutionListener" };
private static final Log logger = LogFactory.getLog(TestContextManager.class);
/**
* Cache of Spring application contexts. This needs to be static, as tests
* may be destroyed and recreated between running individual test methods,
* for example with JUnit.
*/
static final ContextCache contextCache = new ContextCache();
private final TestContext testContext;
private final List<TestExecutionListener> testExecutionListeners = new ArrayList<TestExecutionListener>();
/**
* Delegates to {@link #TestContextManager(Class, String)} with a value of
* <code>null</code> for the default <code>ContextLoader</code> class name.
*/
public TestContextManager(Class<?> testClass) {
this(testClass, null);
}
/**
* Constructs a new <code>TestContextManager</code> for the specified {@link Class test class}
* and automatically {@link #registerTestExecutionListeners registers} the
* {@link TestExecutionListener TestExecutionListeners} configured for the test class
* via the {@link TestExecutionListeners &#064;TestExecutionListeners} annotation.
* @param testClass the test class to be managed
* @param defaultContextLoaderClassName the name of the default
* <code>ContextLoader</code> class to use (may be <code>null</code>)
* @see #registerTestExecutionListeners(TestExecutionListener...)
* @see #retrieveTestExecutionListeners(Class)
*/
public TestContextManager(Class<?> testClass, String defaultContextLoaderClassName) {
this.testContext = new TestContext(testClass, contextCache, defaultContextLoaderClassName);
registerTestExecutionListeners(retrieveTestExecutionListeners(testClass));
}
/**
* Returns the {@link TestContext} managed by this
* <code>TestContextManager</code>.
*/
protected final TestContext getTestContext() {
return this.testContext;
}
/**
* Register the supplied {@link TestExecutionListener TestExecutionListeners}
* by appending them to the set of listeners used by this <code>TestContextManager</code>.
*/
public void registerTestExecutionListeners(TestExecutionListener... testExecutionListeners) {
for (TestExecutionListener listener : testExecutionListeners) {
if (logger.isTraceEnabled()) {
logger.trace("Registering TestExecutionListener: " + listener);
}
this.testExecutionListeners.add(listener);
}
}
/**
* Get the current {@link TestExecutionListener TestExecutionListeners}
* registered for this <code>TestContextManager</code>.
* <p>Allows for modifications, e.g. adding a listener to the beginning of the list.
* However, make sure to keep the list stable while actually executing tests.
*/
public final List<TestExecutionListener> getTestExecutionListeners() {
return this.testExecutionListeners;
}
/**
* Get a copy of the {@link TestExecutionListener TestExecutionListeners}
* registered for this <code>TestContextManager</code> in reverse order.
*/
private List<TestExecutionListener> getReversedTestExecutionListeners() {
List<TestExecutionListener> listenersReversed =
new ArrayList<TestExecutionListener>(getTestExecutionListeners());
Collections.reverse(listenersReversed);
return listenersReversed;
}
/**
* Retrieve an array of newly instantiated {@link TestExecutionListener TestExecutionListeners}
* for the specified {@link Class class}. If {@link TestExecutionListeners &#064;TestExecutionListeners}
* is not <em>present</em> on the supplied class, the default listeners will be returned.
* <p>Note that the {@link TestExecutionListeners#inheritListeners() inheritListeners} flag of
* {@link TestExecutionListeners &#064;TestExecutionListeners} will be taken into consideration.
* Specifically, if the <code>inheritListeners</code> flag is set to <code>true</code>, listeners
* defined in the annotated class will be appended to the listeners defined in superclasses.
* @param clazz the test class for which the listeners should be retrieved
* @return an array of TestExecutionListeners for the specified class
*/
private TestExecutionListener[] retrieveTestExecutionListeners(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<Class<? extends TestExecutionListener>>();
Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(annotationType, clazz);
boolean defaultListeners = false;
// Use defaults?
if (declaringClass == null) {
if (logger.isInfoEnabled()) {
logger.info("@TestExecutionListeners is not present for class [" + clazz + "]: using defaults.");
}
classesList.addAll(getDefaultTestExecutionListenerClasses());
defaultListeners = true;
}
else {
// Traverse the class hierarchy...
while (declaringClass != null) {
TestExecutionListeners testExecutionListeners = declaringClass.getAnnotation(annotationType);
if (logger.isTraceEnabled()) {
logger.trace("Retrieved @TestExecutionListeners [" + testExecutionListeners
+ "] for declaring class [" + declaringClass + "].");
}
Class<? extends TestExecutionListener>[] valueListenerClasses = testExecutionListeners.value();
Class<? extends TestExecutionListener>[] listenerClasses = testExecutionListeners.listeners();
if (!ObjectUtils.isEmpty(valueListenerClasses) && !ObjectUtils.isEmpty(listenerClasses)) {
String msg = String.format(
"Test class [%s] has been configured with @TestExecutionListeners' 'value' [%s] " +
"and 'listeners' [%s] attributes. Use one or the other, but not both.",
declaringClass, ObjectUtils.nullSafeToString(valueListenerClasses),
ObjectUtils.nullSafeToString(listenerClasses));
logger.error(msg);
throw new IllegalStateException(msg);
}
else if (!ObjectUtils.isEmpty(valueListenerClasses)) {
listenerClasses = valueListenerClasses;
}
if (listenerClasses != null) {
classesList.addAll(0, Arrays.<Class<? extends TestExecutionListener>> asList(listenerClasses));
}
declaringClass = (testExecutionListeners.inheritListeners() ?
AnnotationUtils.findAnnotationDeclaringClass(annotationType, declaringClass.getSuperclass()) : null);
}
}
List<TestExecutionListener> listeners = new ArrayList<TestExecutionListener>(classesList.size());
for (Class<? extends TestExecutionListener> listenerClass : classesList) {
try {
listeners.add(BeanUtils.instantiateClass(listenerClass));
}
catch (NoClassDefFoundError err) {
if (defaultListeners) {
if (logger.isDebugEnabled()) {
logger.debug("Could not instantiate default TestExecutionListener class ["
+ listenerClass.getName()
+ "]. Specify custom listener classes or make the default listener classes available.");
}
}
else {
throw err;
}
}
}
return listeners.toArray(new TestExecutionListener[listeners.size()]);
}
/**
* Determine the default {@link TestExecutionListener} classes.
*/
@SuppressWarnings("unchecked")
protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
Set<Class<? extends TestExecutionListener>> defaultListenerClasses =
new LinkedHashSet<Class<? extends TestExecutionListener>>();
for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) {
try {
defaultListenerClasses.add(
(Class<? extends TestExecutionListener>) getClass().getClassLoader().loadClass(className));
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not load default TestExecutionListener class [" + className
+ "]. Specify custom listener classes or make the default listener classes available.");
}
}
}
return defaultListenerClasses;
}
/**
* Hook for pre-processing a test class <em>before</em> execution of any
* tests within the class. Should be called prior to any framework-specific
* <em>before class methods</em> (e.g., methods annotated with JUnit's
* {@link org.junit.BeforeClass &#064;BeforeClass}).
* <p>An attempt will be made to give each registered
* {@link TestExecutionListener} a chance to pre-process the test class
* execution. If a listener throws an exception, however, the remaining
* registered listeners will <strong>not</strong> be called.
* @throws Exception if a registered TestExecutionListener throws an
* exception
* @see #getTestExecutionListeners()
*/
public void beforeTestClass() throws Exception {
final Class<?> testClass = getTestContext().getTestClass();
if (logger.isTraceEnabled()) {
logger.trace("beforeTestClass(): class [" + testClass + "]");
}
getTestContext().updateState(null, null, null);
for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
try {
testExecutionListener.beforeTestClass(getTestContext());
}
catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'before class' callback for test class [" + testClass + "]", ex);
throw ex;
}
}
}
/**
* Hook for preparing a test instance prior to execution of any individual
* test methods, for example for injecting dependencies, etc. Should be
* called immediately after instantiation of the test instance.
* <p>The managed {@link TestContext} will be updated with the supplied
* <code>testInstance</code>.
* <p>An attempt will be made to give each registered
* {@link TestExecutionListener} a chance to prepare the test instance. If a
* listener throws an exception, however, the remaining registered listeners
* will <strong>not</strong> be called.
* @param testInstance the test instance to prepare (never <code>null</code>)
* @throws Exception if a registered TestExecutionListener throws an exception
* @see #getTestExecutionListeners()
*/
public void prepareTestInstance(Object testInstance) throws Exception {
Assert.notNull(testInstance, "testInstance must not be null");
if (logger.isTraceEnabled()) {
logger.trace("prepareTestInstance(): instance [" + testInstance + "]");
}
getTestContext().updateState(testInstance, null, null);
for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
try {
testExecutionListener.prepareTestInstance(getTestContext());
}
catch (Exception ex) {
logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to prepare test instance [" + testInstance + "]", ex);
throw ex;
}
}
}
/**
* Hook for pre-processing a test <em>before</em> execution of the supplied
* {@link Method test method}, for example for setting up test fixtures,
* starting a transaction, etc. Should be called prior to any
* framework-specific <em>before methods</em> (e.g., methods annotated with
* JUnit's {@link org.junit.Before &#064;Before}).
* <p>The managed {@link TestContext} will be updated with the supplied
* <code>testInstance</code> and <code>testMethod</code>.
* <p>An attempt will be made to give each registered
* {@link TestExecutionListener} a chance to pre-process the test method
* execution. If a listener throws an exception, however, the remaining
* registered listeners will <strong>not</strong> be called.
* @param testInstance the current test instance (never <code>null</code>)
* @param testMethod the test method which is about to be executed on the
* test instance
* @throws Exception if a registered TestExecutionListener throws an exception
* @see #getTestExecutionListeners()
*/
public void beforeTestMethod(Object testInstance, Method testMethod) throws Exception {
Assert.notNull(testInstance, "Test instance must not be null");
if (logger.isTraceEnabled()) {
logger.trace("beforeTestMethod(): instance [" + testInstance + "], method [" + testMethod + "]");
}
getTestContext().updateState(testInstance, testMethod, null);
for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {
try {
testExecutionListener.beforeTestMethod(getTestContext());
}
catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'before' execution of test method [" + testMethod + "] for test instance ["
+ testInstance + "]", ex);
throw ex;
}
}
}
/**
* Hook for post-processing a test <em>after</em> execution of the supplied
* {@link Method test method}, for example for tearing down test fixtures,
* ending a transaction, etc. Should be called after any framework-specific
* <em>after methods</em> (e.g., methods annotated with JUnit's
* {@link org.junit.After &#064;After}).
* <p>The managed {@link TestContext} will be updated with the supplied
* <code>testInstance</code>, <code>testMethod</code>, and
* <code>exception</code>.
* <p>Each registered {@link TestExecutionListener} will be given a chance to
* post-process the test method execution. If a listener throws an
* exception, the remaining registered listeners will still be called, but
* the first exception thrown will be tracked and rethrown after all
* listeners have executed. Note that registered listeners will be executed
* in the opposite order in which they were registered.
* @param testInstance the current test instance (never <code>null</code>)
* @param testMethod the test method which has just been executed on the
* test instance
* @param exception the exception that was thrown during execution of the
* test method or by a TestExecutionListener, or <code>null</code> if none
* was thrown
* @throws Exception if a registered TestExecutionListener throws an exception
* @see #getTestExecutionListeners()
*/
public void afterTestMethod(Object testInstance, Method testMethod, Throwable exception) throws Exception {
Assert.notNull(testInstance, "testInstance must not be null");
if (logger.isTraceEnabled()) {
logger.trace("afterTestMethod(): instance [" + testInstance + "], method [" + testMethod + "], exception ["
+ exception + "]");
}
getTestContext().updateState(testInstance, testMethod, exception);
Exception afterTestMethodException = null;
// Traverse the TestExecutionListeners in reverse order to ensure proper
// "wrapper"-style execution of listeners.
for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
try {
testExecutionListener.afterTestMethod(getTestContext());
}
catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'after' execution for test: method [" + testMethod + "], instance ["
+ testInstance + "], exception [" + exception + "]", ex);
if (afterTestMethodException == null) {
afterTestMethodException = ex;
}
}
}
if (afterTestMethodException != null) {
throw afterTestMethodException;
}
}
/**
* Hook for post-processing a test class <em>after</em> execution of all
* tests within the class. Should be called after any framework-specific
* <em>after class methods</em> (e.g., methods annotated with JUnit's
* {@link org.junit.AfterClass &#064;AfterClass}).
* <p>Each registered {@link TestExecutionListener} will be given a chance to
* post-process the test class. If a listener throws an exception, the
* remaining registered listeners will still be called, but the first
* exception thrown will be tracked and rethrown after all listeners have
* executed. Note that registered listeners will be executed in the opposite
* order in which they were registered.
* @throws Exception if a registered TestExecutionListener throws an exception
* @see #getTestExecutionListeners()
*/
public void afterTestClass() throws Exception {
final Class<?> testClass = getTestContext().getTestClass();
if (logger.isTraceEnabled()) {
logger.trace("afterTestClass(): class [" + testClass + "]");
}
getTestContext().updateState(null, null, null);
Exception afterTestClassException = null;
// Traverse the TestExecutionListeners in reverse order to ensure proper
// "wrapper"-style execution of listeners.
for (TestExecutionListener testExecutionListener : getReversedTestExecutionListeners()) {
try {
testExecutionListener.afterTestClass(getTestContext());
}
catch (Exception ex) {
logger.warn("Caught exception while allowing TestExecutionListener [" + testExecutionListener
+ "] to process 'after class' callback for test class [" + testClass + "]", ex);
if (afterTestClassException == null) {
afterTestClassException = ex;
}
}
}
if (afterTestClassException != null) {
throw afterTestClassException;
}
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2002-2009 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.context;
/**
* <p>
* <code>TestExecutionListener</code> defines a <em>listener</em> API for
* reacting to test execution events published by the {@link TestContextManager}
* with which the listener is registered.
* </p>
* <p>
* Concrete implementations must provide a <code>public</code> no-args
* constructor, so that listeners can be instantiated transparently by tools and
* configuration mechanisms.
* </p>
* <p>
* Spring provides the following out-of-the-box implementations:
* </p>
* <ul>
* <li>
* {@link org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* DependencyInjectionTestExecutionListener}</li>
* <li>
* {@link org.springframework.test.context.support.DirtiesContextTestExecutionListener
* DirtiesContextTestExecutionListener}</li>
* <li>
* {@link org.springframework.test.context.transaction.TransactionalTestExecutionListener
* TransactionalTestExecutionListener}</li>
* </ul>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
*/
public interface TestExecutionListener {
/**
* Pre-processes a test class <em>before</em> execution of all tests within
* the class.
* <p>
* This method should be called immediately before framework-specific
* <em>before class</em> lifecycle callbacks.
* <p>
* If a given testing framework (e.g., JUnit 3.8) does not support
* <em>before class</em> lifecycle callbacks, this method will not be called
* for that framework.
*
* @param testContext the test context for the test; never <code>null</code>
* @throws Exception allows any exception to propagate
*/
void beforeTestClass(TestContext testContext) throws Exception;
/**
* Prepares the {@link Object test instance} of the supplied
* {@link TestContext test context}, for example by injecting dependencies.
* <p>
* This method should be called immediately after instantiation of the test
* instance but prior to any framework-specific lifecycle callbacks.
*
* @param testContext the test context for the test; never <code>null</code>
* @throws Exception allows any exception to propagate
*/
void prepareTestInstance(TestContext testContext) throws Exception;
/**
* Pre-processes a test <em>before</em> execution of the
* {@link java.lang.reflect.Method test method} in the supplied
* {@link TestContext test context}, for example by setting up test
* fixtures.
* <p>
* This method should be called immediately prior to framework-specific
* <em>before</em> lifecycle callbacks.
*
* @param testContext the test context in which the test method will be
* executed; never <code>null</code>
* @throws Exception allows any exception to propagate
*/
void beforeTestMethod(TestContext testContext) throws Exception;
/**
* Post-processes a test <em>after</em> execution of the
* {@link java.lang.reflect.Method test method} in the supplied
* {@link TestContext test context}, for example by tearing down test
* fixtures.
* <p>
* This method should be called immediately after framework-specific
* <em>after</em> lifecycle callbacks.
*
* @param testContext the test context in which the test method was
* executed; never <code>null</code>
* @throws Exception allows any exception to propagate
*/
void afterTestMethod(TestContext testContext) throws Exception;
/**
* Post-processes a test class <em>after</em> execution of all tests within
* the class.
* <p>
* This method should be called immediately after framework-specific
* <em>after class</em> lifecycle callbacks.
* <p>
* If a given testing framework (e.g., JUnit 3.8) does not support
* <em>after class</em> lifecycle callbacks, this method will not be called
* for that framework.
*
* @param testContext the test context for the test; never <code>null</code>
* @throws Exception allows any exception to propagate
*/
void afterTestClass(TestContext testContext) throws Exception;
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2002-2009 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.context;
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;
/**
* <code>TestExecutionListeners</code> defines class-level metadata for
* configuring which {@link TestExecutionListener TestExecutionListeners} should
* be registered with a {@link TestContextManager}. Typically,
* <code>&#064;TestExecutionListeners</code> will be used in conjunction with
* {@link ContextConfiguration &#064;ContextConfiguration}.
*
* @author Sam Brannen
* @since 2.5
* @see TestExecutionListener
* @see TestContextManager
* @see ContextConfiguration
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestExecutionListeners {
/**
* <p>
* The {@link TestExecutionListener TestExecutionListeners} to register with
* a {@link TestContextManager}.
* </p>
*
* @see org.springframework.test.context.support.DependencyInjectionTestExecutionListener
* @see org.springframework.test.context.support.DirtiesContextTestExecutionListener
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
*/
Class<? extends TestExecutionListener>[] listeners() default {};
/**
* Alias for {@link #listeners() listeners}.
*/
Class<? extends TestExecutionListener>[] value() default {};
/**
* <p>
* Whether or not {@link #value() TestExecutionListeners} from superclasses
* should be <em>inherited</em>.
* </p>
* <p>
* The default value is <code>true</code>, which means that an annotated
* class will <em>inherit</em> the listeners defined by an annotated
* superclass. Specifically, the listeners for an annotated class will be
* appended to the list of listeners defined by an annotated superclass.
* Thus, subclasses have the option of <em>extending</em> the list of
* listeners. In the following example, <code>AbstractBaseTest</code> will
* be configured with <code>DependencyInjectionTestExecutionListener</code>
* and <code>DirtiesContextTestExecutionListener</code>; whereas,
* <code>TransactionalTest</code> will be configured with
* <code>DependencyInjectionTestExecutionListener</code>,
* <code>DirtiesContextTestExecutionListener</code>, <strong>and</strong>
* <code>TransactionalTestExecutionListener</code>, in that order.
* </p>
*
* <pre class="code">
* &#064;TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
* DirtiesContextTestExecutionListener.class })
* public abstract class AbstractBaseTest {
* // ...
* }
*
* &#064;TestExecutionListeners(TransactionalTestExecutionListener.class)
* public class TransactionalTest extends AbstractBaseTest {
* // ...
* }
* </pre>
*
* <p>
* If <code>inheritListeners</code> is set to <code>false</code>, the
* listeners for the annotated class will <em>shadow</em> and effectively
* replace any listeners defined by a superclass.
* </p>
*/
boolean inheritListeners() default true;
}

View File

@@ -0,0 +1,399 @@
/*
* Copyright 2002-2009 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.context.junit38;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.annotation.ExpectedException;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.annotation.ProfileValueSource;
import org.springframework.test.annotation.ProfileValueUtils;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.annotation.Timed;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
/**
* <p>
* Abstract base {@link TestCase} which integrates the <i>Spring TestContext
* Framework</i> and explicit {@link ApplicationContext} testing support in a
* <strong>JUnit 3.8</strong> environment.
* </p>
* <p>
* Concrete subclasses:
* </p>
* <ul>
* <li>Typically declare a class-level
* {@link org.springframework.test.context.ContextConfiguration
* &#064;ContextConfiguration} annotation to configure the
* {@link ApplicationContext application context}
* {@link org.springframework.test.context.ContextConfiguration#locations()
* resource locations}. <i>If your test does not need to load an application
* context, you may choose to omit the
* {@link org.springframework.test.context.ContextConfiguration
* &#064;ContextConfiguration} declaration and configure the appropriate
* {@link org.springframework.test.context.TestExecutionListener
* TestExecutionListeners} manually.</i></li>
* <li>Must declare public constructors which match the signatures of
* {@link #AbstractJUnit38SpringContextTests()
* AbstractJUnit38SpringContextTests()} and
* {@link #AbstractJUnit38SpringContextTests(String)
* AbstractJUnit38SpringContextTests(String)} and delegate to
* <code>super();</code> and <code>super(name);</code> respectively.</li>
* </ul>
* <p>
* The following list constitutes all annotations currently supported directly
* by <code>AbstractJUnit38SpringContextTests</code>. <i>(Note that additional
* annotations may be supported by various
* {@link org.springframework.test.context.TestExecutionListener
* TestExecutionListeners})</i>
* </p>
* <ul>
* <li>{@link org.springframework.test.annotation.DirtiesContext
* &#064;DirtiesContext} (via the configured
* {@link DirtiesContextTestExecutionListener}; only supported on methods for
* JUnit 3.8)</li>
* <li>
* {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
* &#064;ProfileValueSourceConfiguration}</li>
* <li>{@link IfProfileValue &#064;IfProfileValue}</li>
* <li>{@link ExpectedException &#064;ExpectedException}</li>
* <li>{@link Timed &#064;Timed}</li>
* <li>{@link Repeat &#064;Repeat}</li>
* </ul>
* <p>
* JUnit 3.8 does not support <i>before class</i> or <i>after class</i>
* lifecycle callbacks. The following
* {@link org.springframework.test.context.TestExecutionListener
* TestExecutionListener} methods are therefore unsupported in a JUnit 3.8
* environment:
* <ul>
* <li>
* {@link org.springframework.test.context.TestExecutionListener#beforeTestClass(org.springframework.test.context.TestContext)
* beforeTestClass()}</li>
* <li>
* {@link org.springframework.test.context.TestExecutionListener#afterTestClass(org.springframework.test.context.TestContext)
* afterTestClass()}</li>
* </ul>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see org.springframework.test.context.TestContext
* @see org.springframework.test.context.TestContextManager
* @see org.springframework.test.context.TestExecutionListeners
* @see AbstractTransactionalJUnit38SpringContextTests
* @see org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests
* @see org.springframework.test.context.testng.AbstractTestNGSpringContextTests
* @deprecated as of Spring 3.1, in favor of using
* {@link org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests AbstractJUnit4SpringContextTests}
*/
@Deprecated
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
public abstract class AbstractJUnit38SpringContextTests extends TestCase implements ApplicationContextAware {
private static int disabledTestCount = 0;
/**
* 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());
/**
* The {@link ApplicationContext} that was injected into this test instance
* via {@link #setApplicationContext(ApplicationContext)}.
*/
protected ApplicationContext applicationContext;
/**
* {@link ProfileValueSource} available to subclasses but primarily intended
* for internal use to provide support for {@link IfProfileValue
* &#064;IfProfileValue}.
*/
protected final ProfileValueSource profileValueSource;
private final TestContextManager testContextManager;
/**
* Constructs a new AbstractJUnit38SpringContextTests instance; initializes
* the internal {@link TestContextManager} for the current test; and
* retrieves the configured (or default) {@link ProfileValueSource}.
*/
public AbstractJUnit38SpringContextTests() {
super();
this.testContextManager = new TestContextManager(getClass());
this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
}
/**
* Constructs a new AbstractJUnit38SpringContextTests instance with the
* supplied <code>name</code>; initializes the internal
* {@link TestContextManager} for the current test; and retrieves the
* configured (or default) {@link ProfileValueSource}.
*
* @param name the name of the current test to execute
*/
public AbstractJUnit38SpringContextTests(String name) {
super(name);
this.testContextManager = new TestContextManager(getClass());
this.profileValueSource = ProfileValueUtils.retrieveProfileValueSource(getClass());
}
/**
* Sets the {@link ApplicationContext} to be used by this test instance,
* provided via {@link ApplicationContextAware} semantics.
*/
public final void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Runs the <em>Spring TestContext Framework</em> test sequence.
* <p>
* In addition to standard {@link TestCase#runBare()} semantics, this
* implementation performs the following:
* <ul>
* <li>Calls {@link TestContextManager#prepareTestInstance(Object)
* prepareTestInstance()},
* {@link TestContextManager#beforeTestMethod(Object,Method)
* beforeTestMethod()}, and
* {@link TestContextManager#afterTestMethod(Object,Method,Throwable)
* afterTestMethod()} on this test's {@link TestContextManager} at the
* appropriate test execution points.</li>
* <li>Provides support for {@link IfProfileValue &#064;IfProfileValue}.</li>
* <li>Provides support for {@link Repeat &#064;Repeat}.</li>
* <li>Provides support for {@link Timed &#064;Timed}.</li>
* <li>Provides support for {@link ExpectedException
* &#064;ExpectedException}.</li>
* </ul>
*
* @see ProfileValueUtils#isTestEnabledInThisEnvironment
*/
@Override
public void runBare() throws Throwable {
this.testContextManager.prepareTestInstance(this);
final Method testMethod = getTestMethod();
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(this.profileValueSource, testMethod, getClass())) {
recordDisabled(testMethod);
return;
}
runTestTimed(new TestExecutionCallback() {
public void run() throws Throwable {
runManaged(testMethod);
}
}, testMethod);
}
/**
* Get the current test method.
*/
private Method getTestMethod() {
assertNotNull("TestCase.getName() cannot be null", getName());
Method testMethod = null;
try {
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;
}
/**
* Runs a <em>timed</em> test via the supplied {@link TestExecutionCallback}
* , providing support for the {@link Timed &#064;Timed} annotation.
*
* @param tec the test execution callback to run
* @param testMethod the actual test method: used to retrieve the
* <code>timeout</code>
* @throws Throwable if any exception is thrown
* @see Timed
* @see #runTest
*/
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());
}
}
}
}
/**
* Runs a test via the supplied {@link TestExecutionCallback}, providing
* support for the {@link ExpectedException &#064;ExpectedException} and
* {@link Repeat &#064;Repeat} annotations.
*
* @param tec the test execution callback to run
* @param testMethod the actual test method: used to retrieve the
* {@link ExpectedException &#064;ExpectedException} and {@link Repeat
* &#064;Repeat} annotations
* @throws Throwable if any exception is thrown
* @see ExpectedException
* @see Repeat
*/
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.isInfoEnabled()) {
this.logger.info("Repetition " + (i + 1) + " of test " + testMethod.getName());
}
tec.run();
if (exceptionIsExpected) {
fail("Expected exception: " + expectedException.getName());
}
}
catch (Throwable ex) {
if (!exceptionIsExpected) {
throw ex;
}
if (!expectedException.isAssignableFrom(ex.getClass())) {
// Wrap the unexpected throwable with an explicit message.
AssertionFailedError assertionError = new AssertionFailedError("Unexpected exception, expected <"
+ expectedException.getName() + "> but was <" + ex.getClass().getName() + ">");
assertionError.initCause(ex);
throw assertionError;
}
}
}
}
/**
* Calls {@link TestContextManager#beforeTestMethod(Object,Method)} and
* {@link TestContextManager#afterTestMethod(Object,Method,Throwable)} at
* the appropriate test execution points.
*
* @param testMethod the test method to run
* @throws Throwable if any exception is thrown
* @see #runBare()
* @see TestCase#runTest()
*/
private void runManaged(Method testMethod) throws Throwable {
Throwable exception = null;
boolean reachedTest = false;
try {
this.testContextManager.beforeTestMethod(this, testMethod);
setUp();
reachedTest = true;
runTest();
}
catch (Throwable ex) {
exception = ex;
}
finally {
try {
if (reachedTest) {
tearDown();
}
}
catch (Throwable ex) {
if (exception == null) {
exception = ex;
}
}
finally {
try {
this.testContextManager.afterTestMethod(this, testMethod, exception);
}
catch (Throwable ex) {
if (exception == null) {
exception = ex;
}
}
}
}
if (exception != null) {
if (exception.getCause() instanceof AssertionError) {
exception = exception.getCause();
}
throw exception;
}
}
/**
* Records the supplied test method as <em>disabled</em> in the current
* environment by incrementing the total number of disabled tests and
* logging a debug message.
*
* @param testMethod the test method that is disabled.
* @see #getDisabledTestCount()
*/
protected void recordDisabled(Method testMethod) {
disabledTestCount++;
if (this.logger.isInfoEnabled()) {
this.logger.info("**** " + getClass().getName() + "." + getName() + "() is disabled in this environment. "
+ "Total disabled tests = " + getDisabledTestCount());
}
}
/**
* Private inner class that defines a callback analogous to {@link Runnable}
* , just declaring Throwable.
*/
private static interface TestExecutionCallback {
void run() throws Throwable;
}
}

View File

@@ -0,0 +1,158 @@
/*
* 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.context.junit38;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.jdbc.SimpleJdbcTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
* Abstract {@link Transactional transactional} extension of
* {@link AbstractJUnit38SpringContextTests} which adds convenience
* functionality for JDBC access. Expects a {@link javax.sql.DataSource} bean
* and a {@link PlatformTransactionManager} bean to be defined in the Spring
* {@link ApplicationContext application context}.
* </p>
* <p>
* This class exposes a {@link SimpleJdbcTemplate} and provides an easy way to
* {@link #countRowsInTable(String) count the number of rows in a table} ,
* {@link #deleteFromTables(String...) delete from the database} , and
* {@link #executeSqlScript(String, boolean) execute SQL scripts} within a
* transaction.
* </p>
* <p>
* Concrete subclasses must fulfill the same requirements outlined in
* {@link AbstractJUnit38SpringContextTests}.
* </p>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see AbstractJUnit38SpringContextTests
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.TestExecutionListeners
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.test.context.transaction.TransactionConfiguration
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.annotation.NotTransactional
* @see org.springframework.test.annotation.Rollback
* @see org.springframework.test.context.transaction.BeforeTransaction
* @see org.springframework.test.context.transaction.AfterTransaction
* @see org.springframework.test.jdbc.SimpleJdbcTestUtils
* @see org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
* @see org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
* @deprecated as of Spring 3.1, in favor of using
* {@link org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests AbstractTransactionalJUnit4SpringContextTests}
*/
@Deprecated
@TestExecutionListeners(TransactionalTestExecutionListener.class)
@Transactional
public abstract class AbstractTransactionalJUnit38SpringContextTests extends AbstractJUnit38SpringContextTests {
/**
* The SimpleJdbcTemplate that this base class manages, available to subclasses.
*/
protected SimpleJdbcTemplate simpleJdbcTemplate;
private String sqlScriptEncoding;
/**
* Constructs a new AbstractTransactionalJUnit38SpringContextTests instance.
*/
public AbstractTransactionalJUnit38SpringContextTests() {
super();
}
/**
* Constructs a new AbstractTransactionalJUnit38SpringContextTests instance
* with the supplied <code>name</code>.
* @param name the name of the current test to execute
*/
public AbstractTransactionalJUnit38SpringContextTests(String name) {
super(name);
}
/**
* Set the DataSource, typically provided via Dependency Injection.
* @param dataSource The DataSource to inject
*/
@Autowired
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
/**
* Specify the encoding for SQL scripts, if different from the platform encoding.
* @see #executeSqlScript
*/
public void setSqlScriptEncoding(String sqlScriptEncoding) {
this.sqlScriptEncoding = sqlScriptEncoding;
}
/**
* 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 SimpleJdbcTestUtils.countRowsInTable(this.simpleJdbcTemplate, tableName);
}
/**
* Convenience method for deleting all rows from the specified tables.
* Use with caution outside of a transaction!
* @param names the names of the tables from which to delete
* @return the total number of rows deleted from all specified tables
*/
protected int deleteFromTables(String... names) {
return SimpleJdbcTestUtils.deleteFromTables(this.simpleJdbcTemplate, names);
}
/**
* Execute the given SQL script. Use with caution outside of a transaction!
* <p>The script will normally be loaded by classpath. There should be one statement
* per line. Any semicolons will be removed. <b>Do not use this method to execute
* DDL if you expect rollback.</b>
* @param sqlResourcePath the Spring resource path for the SQL script
* @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 <code>false</code>
*/
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError)
throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
SimpleJdbcTestUtils.executeSqlScript(
this.simpleJdbcTemplate, new EncodedResource(resource, this.sqlScriptEncoding), continueOnError);
}
}

View File

@@ -0,0 +1,7 @@
/**
* <p>Support classes for ApplicationContext-based and transactional
* tests run with JUnit 3.8 and the <em>Spring TestContext Framework</em>.</p>
*/
package org.springframework.test.context.junit38;

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2002-2009 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.context.junit4;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
/**
* <p>
* Abstract base test class which integrates the <em>Spring TestContext
* Framework</em> with explicit {@link ApplicationContext} testing support in a
* <strong>JUnit 4.5+</strong> environment.
* </p>
* <p>
* Concrete subclasses should typically declare a class-level
* {@link ContextConfiguration &#064;ContextConfiguration} annotation to
* configure the {@link ApplicationContext application context}
* {@link ContextConfiguration#locations() resource locations}.
* <em>If your test does not need to load an application context, you may choose
* to omit the {@link ContextConfiguration &#064;ContextConfiguration} declaration
* and to configure the appropriate
* {@link org.springframework.test.context.TestExecutionListener TestExecutionListeners}
* manually.</em>
* </p>
* <p>
* Note: this class serves only as a convenience for extension. If you do not
* wish for your test classes to be tied to a Spring-specific class hierarchy,
* you may configure your own custom test classes by using
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration
* &#064;ContextConfiguration}, {@link TestExecutionListeners
* &#064;TestExecutionListeners}, etc.
* </p>
*
* @author Sam Brannen
* @since 2.5
* @see ContextConfiguration
* @see TestContext
* @see TestContextManager
* @see AbstractTransactionalJUnit4SpringContextTests
* @see org.springframework.test.context.junit38.AbstractJUnit38SpringContextTests
* @see org.springframework.test.context.testng.AbstractTestNGSpringContextTests
*/
@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
public abstract class AbstractJUnit4SpringContextTests implements ApplicationContextAware {
/**
* Logger available to subclasses.
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* The {@link ApplicationContext} that was injected into this test instance
* via {@link #setApplicationContext(ApplicationContext)}.
*/
protected ApplicationContext applicationContext;
/**
* Set the {@link ApplicationContext} to be used by this test instance,
* provided via {@link ApplicationContextAware} semantics.
*/
public final void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}

View File

@@ -0,0 +1,145 @@
/*
* Copyright 2002-2011 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.context.junit4;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.test.jdbc.SimpleJdbcTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
* Abstract {@link Transactional transactional} extension of
* {@link AbstractJUnit4SpringContextTests} which adds convenience functionality
* for JDBC access. Expects a {@link DataSource} bean and a
* {@link PlatformTransactionManager} bean to be defined in the Spring
* {@link ApplicationContext application context}.
* </p>
* <p>
* This class exposes a {@link SimpleJdbcTemplate} and provides an easy way to
* {@link #countRowsInTable(String) count the number of rows in a table} ,
* {@link #deleteFromTables(String...) delete from the database} , and
* {@link #executeSqlScript(String, boolean) execute SQL scripts} within a
* transaction.
* </p>
* <p>
* Concrete subclasses must fulfill the same requirements outlined in
* {@link AbstractJUnit4SpringContextTests}.
* </p>
* <p>
* Note: this class serves only as a convenience for extension. If you do not
* wish for your test classes to be tied to a Spring-specific class hierarchy,
* you may configure your own custom test classes by using
* {@link SpringJUnit4ClassRunner}, {@link ContextConfiguration
* &#064;ContextConfiguration}, {@link TestExecutionListeners
* &#064;TestExecutionListeners}, {@link Transactional &#064;Transactional},
* etc.
* </p>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see AbstractJUnit4SpringContextTests
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.TestExecutionListeners
* @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
* @see org.springframework.test.context.transaction.TransactionConfiguration
* @see org.springframework.transaction.annotation.Transactional
* @see org.springframework.test.annotation.NotTransactional
* @see org.springframework.test.annotation.Rollback
* @see org.springframework.test.context.transaction.BeforeTransaction
* @see org.springframework.test.context.transaction.AfterTransaction
* @see org.springframework.test.jdbc.SimpleJdbcTestUtils
* @see org.springframework.test.context.junit38.AbstractTransactionalJUnit38SpringContextTests
* @see org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
*/
@SuppressWarnings("deprecation")
@TestExecutionListeners(TransactionalTestExecutionListener.class)
@Transactional
public abstract class AbstractTransactionalJUnit4SpringContextTests extends AbstractJUnit4SpringContextTests {
/**
* The SimpleJdbcTemplate that this base class manages, available to subclasses.
*/
protected SimpleJdbcTemplate simpleJdbcTemplate;
private String sqlScriptEncoding;
/**
* Set the DataSource, typically provided via Dependency Injection.
*/
@Autowired
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
/**
* Specify the encoding for SQL scripts, if different from the platform encoding.
* @see #executeSqlScript
*/
public void setSqlScriptEncoding(String sqlScriptEncoding) {
this.sqlScriptEncoding = sqlScriptEncoding;
}
/**
* 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 SimpleJdbcTestUtils.countRowsInTable(this.simpleJdbcTemplate, tableName);
}
/**
* Convenience method for deleting all rows from the specified tables. Use
* with caution outside of a transaction!
* @param names the names of the tables from which to delete
* @return the total number of rows deleted from all specified tables
*/
protected int deleteFromTables(String... names) {
return SimpleJdbcTestUtils.deleteFromTables(this.simpleJdbcTemplate, names);
}
/**
* Execute the given SQL script. Use with caution outside of a transaction!
* <p>The script will normally be loaded by classpath. There should be one
* statement per line. Any semicolons will be removed. <b>Do not use this
* method to execute DDL if you expect rollback.</b>
* @param sqlResourcePath the Spring resource path for the SQL script
* @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 <code>false</code>
*/
protected void executeSqlScript(String sqlResourcePath, boolean continueOnError) throws DataAccessException {
Resource resource = this.applicationContext.getResource(sqlResourcePath);
SimpleJdbcTestUtils.executeSqlScript(this.simpleJdbcTemplate, new EncodedResource(resource,
this.sqlScriptEncoding), continueOnError);
}
}

View File

@@ -0,0 +1,474 @@
/*
* Copyright 2002-2011 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.context.junit4;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.internal.AssumptionViolatedException;
import org.junit.internal.runners.model.EachTestNotifier;
import org.junit.internal.runners.model.ReflectiveCallable;
import org.junit.internal.runners.statements.ExpectException;
import org.junit.internal.runners.statements.Fail;
import org.junit.internal.runners.statements.FailOnTimeout;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.springframework.test.annotation.ExpectedException;
import org.springframework.test.annotation.ProfileValueUtils;
import org.springframework.test.annotation.Repeat;
import org.springframework.test.annotation.Timed;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks;
import org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks;
import org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks;
import org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks;
import org.springframework.test.context.junit4.statements.SpringFailOnTimeout;
import org.springframework.test.context.junit4.statements.SpringRepeat;
import org.springframework.util.ReflectionUtils;
/**
* <p>
* <code>SpringJUnit4ClassRunner</code> is a custom extension of
* {@link BlockJUnit4ClassRunner} which provides functionality of the
* <em>Spring TestContext Framework</em> to standard JUnit 4.5+ tests by means
* of the {@link TestContextManager} and associated support classes and
* annotations.
* </p>
* <p>
* The following list constitutes all annotations currently supported directly
* by <code>SpringJUnit4ClassRunner</code>.
* <em>(Note that additional annotations may be supported by various
* {@link org.springframework.test.context.TestExecutionListener
* TestExecutionListeners})</em>
* </p>
* <ul>
* <li>{@link Test#expected() &#064;Test(expected=...)}</li>
* <li>{@link ExpectedException &#064;ExpectedException}</li>
* <li>{@link Test#timeout() &#064;Test(timeout=...)}</li>
* <li>{@link Timed &#064;Timed}</li>
* <li>{@link Repeat &#064;Repeat}</li>
* <li>{@link Ignore &#064;Ignore}</li>
* <li>
* {@link org.springframework.test.annotation.ProfileValueSourceConfiguration
* &#064;ProfileValueSourceConfiguration}</li>
* <li>{@link org.springframework.test.annotation.IfProfileValue
* &#064;IfProfileValue}</li>
* </ul>
* <p>
* <b>NOTE:</b> As of Spring 3.0, <code>SpringJUnit4ClassRunner</code> requires
* JUnit 4.5+.
* </p>
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see TestContextManager
*/
@SuppressWarnings("deprecation")
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
private static final Log logger = LogFactory.getLog(SpringJUnit4ClassRunner.class);
private final TestContextManager testContextManager;
/**
* Constructs a new <code>SpringJUnit4ClassRunner</code> and initializes a
* {@link TestContextManager} to provide Spring testing functionality to
* standard JUnit tests.
* @param clazz the test class to be run
* @see #createTestContextManager(Class)
*/
public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError {
super(clazz);
if (logger.isDebugEnabled()) {
logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "].");
}
this.testContextManager = createTestContextManager(clazz);
}
/**
* Creates a new {@link TestContextManager} for the supplied test class and
* the configured <em>default <code>ContextLoader</code> class name</em>.
* Can be overridden by subclasses.
* @param clazz the test class to be managed
* @see #getDefaultContextLoaderClassName(Class)
*/
protected TestContextManager createTestContextManager(Class<?> clazz) {
return new TestContextManager(clazz, getDefaultContextLoaderClassName(clazz));
}
/**
* Get the {@link TestContextManager} associated with this runner.
*/
protected final TestContextManager getTestContextManager() {
return this.testContextManager;
}
/**
* Get the name of the default <code>ContextLoader</code> class to use for
* the supplied test class. The named class will be used if the test class
* does not explicitly declare a <code>ContextLoader</code> class via the
* <code>&#064;ContextConfiguration</code> annotation.
* <p>The default implementation returns <code>null</code>, thus implying use
* of the <em>standard</em> default <code>ContextLoader</code> class name.
* Can be overridden by subclasses.
* @param clazz the test class
* @return <code>null</code>
*/
protected String getDefaultContextLoaderClassName(Class<?> clazz) {
return null;
}
/**
* Returns a description suitable for an ignored test class if the test is
* disabled via <code>&#064;IfProfileValue</code> at the class-level, and
* otherwise delegates to the parent implementation.
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
*/
@Override
public Description getDescription() {
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
return Description.createSuiteDescription(getTestClass().getJavaClass());
}
return super.getDescription();
}
/**
* Check whether the test is enabled in the first place. This prevents
* classes with a non-matching <code>&#064;IfProfileValue</code> annotation
* from running altogether, even skipping the execution of
* <code>prepareTestInstance()</code> <code>TestExecutionListener</code>
* methods.
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Class)
* @see org.springframework.test.annotation.IfProfileValue
* @see org.springframework.test.context.TestExecutionListener
*/
@Override
public void run(RunNotifier notifier) {
if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
notifier.fireTestIgnored(getDescription());
return;
}
super.run(notifier);
}
/**
* Wraps the {@link Statement} returned by the parent implementation with a
* {@link RunBeforeTestClassCallbacks} statement, thus preserving the
* default functionality but adding support for the Spring TestContext
* Framework.
* @see RunBeforeTestClassCallbacks
*/
@Override
protected Statement withBeforeClasses(Statement statement) {
Statement junitBeforeClasses = super.withBeforeClasses(statement);
return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager());
}
/**
* Wraps the {@link Statement} returned by the parent implementation with a
* {@link RunAfterTestClassCallbacks} statement, thus preserving the default
* functionality but adding support for the Spring TestContext Framework.
* @see RunAfterTestClassCallbacks
*/
@Override
protected Statement withAfterClasses(Statement statement) {
Statement junitAfterClasses = super.withAfterClasses(statement);
return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager());
}
/**
* Delegates to the parent implementation for creating the test instance and
* then allows the {@link #getTestContextManager() TestContextManager} to
* prepare the test instance before returning it.
* @see TestContextManager#prepareTestInstance(Object)
*/
@Override
protected Object createTest() throws Exception {
Object testInstance = super.createTest();
getTestContextManager().prepareTestInstance(testInstance);
return testInstance;
}
/**
* Performs the same logic as
* {@link BlockJUnit4ClassRunner#runChild(FrameworkMethod, RunNotifier)},
* except that tests are determined to be <em>ignored</em> by
* {@link #isTestMethodIgnored(FrameworkMethod)}.
*/
@Override
protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) {
EachTestNotifier eachNotifier = springMakeNotifier(frameworkMethod, notifier);
if (isTestMethodIgnored(frameworkMethod)) {
eachNotifier.fireTestIgnored();
return;
}
eachNotifier.fireTestStarted();
try {
methodBlock(frameworkMethod).evaluate();
}
catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
}
catch (Throwable e) {
eachNotifier.addFailure(e);
}
finally {
eachNotifier.fireTestFinished();
}
}
/**
* <code>springMakeNotifier()</code> is an exact copy of
* {@link BlockJUnit4ClassRunner BlockJUnit4ClassRunner's}
* <code>makeNotifier()</code> method, but we have decided to prefix it with
* "spring" and keep it <code>private</code> in order to avoid the
* compatibility clashes that were introduced in JUnit between versions 4.5,
* 4.6, and 4.7.
*/
private EachTestNotifier springMakeNotifier(FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
return new EachTestNotifier(notifier, description);
}
/**
* Augments the default JUnit behavior
* {@link #withPotentialRepeat(FrameworkMethod, Object, Statement) with
* potential repeats} of the entire execution chain.
* <p>Furthermore, support for timeouts has been moved down the execution chain
* in order to include execution of {@link org.junit.Before &#064;Before}
* and {@link org.junit.After &#064;After} methods within the timed
* execution. Note that this differs from the default JUnit behavior of
* executing <code>&#064;Before</code> and <code>&#064;After</code> methods
* in the main thread while executing the actual test method in a separate
* thread. Thus, the end effect is that <code>&#064;Before</code> and
* <code>&#064;After</code> methods will be executed in the same thread as
* the test method. As a consequence, JUnit-specified timeouts will work
* fine in combination with Spring transactions. Note that JUnit-specific
* timeouts still differ from Spring-specific timeouts in that the former
* execute in a separate thread while the latter simply execute in the main
* thread (like regular tests).
* @see #possiblyExpectingExceptions(FrameworkMethod, Object, Statement)
* @see #withBefores(FrameworkMethod, Object, Statement)
* @see #withAfters(FrameworkMethod, Object, Statement)
* @see #withPotentialTimeout(FrameworkMethod, Object, Statement)
* @see #withPotentialRepeat(FrameworkMethod, Object, Statement)
*/
@Override
protected Statement methodBlock(FrameworkMethod frameworkMethod) {
Object testInstance;
try {
testInstance = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest();
}
}.run();
}
catch (Throwable ex) {
return new Fail(ex);
}
Statement statement = methodInvoker(frameworkMethod, testInstance);
statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement);
statement = withBefores(frameworkMethod, testInstance, statement);
statement = withAfters(frameworkMethod, testInstance, statement);
statement = withRulesReflectively(frameworkMethod, testInstance, statement);
statement = withPotentialRepeat(frameworkMethod, testInstance, statement);
statement = withPotentialTimeout(frameworkMethod, testInstance, statement);
return statement;
}
/**
* Invokes JUnit 4.7's private <code>withRules()</code> method using
* reflection. This is necessary for backwards compatibility with the JUnit
* 4.5 and 4.6 implementations of {@link BlockJUnit4ClassRunner}.
*/
private Statement withRulesReflectively(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
Method withRulesMethod = ReflectionUtils.findMethod(getClass(), "withRules", FrameworkMethod.class,
Object.class, Statement.class);
if (withRulesMethod != null) {
// Original JUnit 4.7 code:
// statement = withRules(frameworkMethod, testInstance, statement);
ReflectionUtils.makeAccessible(withRulesMethod);
statement = (Statement) ReflectionUtils.invokeMethod(withRulesMethod, this, frameworkMethod, testInstance,
statement);
}
return statement;
}
/**
* Returns <code>true</code> if {@link Ignore &#064;Ignore} is present for
* the supplied {@link FrameworkMethod test method} or if the test method is
* disabled via <code>&#064;IfProfileValue</code>.
* @see ProfileValueUtils#isTestEnabledInThisEnvironment(Method, Class)
*/
protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) {
Method method = frameworkMethod.getMethod();
return (method.isAnnotationPresent(Ignore.class) || !ProfileValueUtils.isTestEnabledInThisEnvironment(method,
getTestClass().getJavaClass()));
}
/**
* Performs the same logic as
* {@link BlockJUnit4ClassRunner#possiblyExpectingExceptions(FrameworkMethod, Object, Statement)}
* except that the <em>expected exception</em> is retrieved using
* {@link #getExpectedException(FrameworkMethod)}.
*/
@Override
protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
Class<? extends Throwable> expectedException = getExpectedException(frameworkMethod);
return expectedException != null ? new ExpectException(next, expectedException) : next;
}
/**
* Get the <code>exception</code> that the supplied {@link FrameworkMethod
* test method} is expected to throw.
* <p>Supports both Spring's {@link ExpectedException @ExpectedException(...)}
* and JUnit's {@link Test#expected() @Test(expected=...)} annotations, but
* not both simultaneously.
* @return the expected exception, or <code>null</code> if none was specified
*/
protected Class<? extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) {
Test testAnnotation = frameworkMethod.getAnnotation(Test.class);
Class<? extends Throwable> junitExpectedException = (testAnnotation != null
&& testAnnotation.expected() != Test.None.class ? testAnnotation.expected() : null);
ExpectedException expectedExAnn = frameworkMethod.getAnnotation(ExpectedException.class);
Class<? extends Throwable> springExpectedException = (expectedExAnn != null ? expectedExAnn.value() : null);
if (springExpectedException != null && junitExpectedException != null) {
String msg = "Test method [" + frameworkMethod.getMethod()
+ "] has been configured with Spring's @ExpectedException(" + springExpectedException.getName()
+ ".class) and JUnit's @Test(expected=" + junitExpectedException.getName()
+ ".class) annotations. "
+ "Only one declaration of an 'expected exception' is permitted per test method.";
logger.error(msg);
throw new IllegalStateException(msg);
}
return springExpectedException != null ? springExpectedException : junitExpectedException;
}
/**
* Supports both Spring's {@link Timed &#064;Timed} and JUnit's
* {@link Test#timeout() &#064;Test(timeout=...)} annotations, but not both
* simultaneously. Returns either a {@link SpringFailOnTimeout}, a
* {@link FailOnTimeout}, or the unmodified, supplied {@link Statement} as
* appropriate.
* @see #getSpringTimeout(FrameworkMethod)
* @see #getJUnitTimeout(FrameworkMethod)
*/
@Override
protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
Statement statement = null;
long springTimeout = getSpringTimeout(frameworkMethod);
long junitTimeout = getJUnitTimeout(frameworkMethod);
if (springTimeout > 0 && junitTimeout > 0) {
String msg = "Test method [" + frameworkMethod.getMethod()
+ "] has been configured with Spring's @Timed(millis=" + springTimeout
+ ") and JUnit's @Test(timeout=" + junitTimeout
+ ") annotations. Only one declaration of a 'timeout' is permitted per test method.";
logger.error(msg);
throw new IllegalStateException(msg);
}
else if (springTimeout > 0) {
statement = new SpringFailOnTimeout(next, springTimeout);
}
else if (junitTimeout > 0) {
statement = new FailOnTimeout(next, junitTimeout);
}
else {
statement = next;
}
return statement;
}
/**
* Retrieves the configured JUnit <code>timeout</code> from the {@link Test
* &#064;Test} annotation on the supplied {@link FrameworkMethod test method}.
* @return the timeout, or <code>0</code> if none was specified.
*/
protected long getJUnitTimeout(FrameworkMethod frameworkMethod) {
Test testAnnotation = frameworkMethod.getAnnotation(Test.class);
return (testAnnotation != null && testAnnotation.timeout() > 0 ? testAnnotation.timeout() : 0);
}
/**
* Retrieves the configured Spring-specific <code>timeout</code> from the
* {@link Timed &#064;Timed} annotation on the supplied
* {@link FrameworkMethod test method}.
* @return the timeout, or <code>0</code> if none was specified.
*/
protected long getSpringTimeout(FrameworkMethod frameworkMethod) {
Timed timedAnnotation = frameworkMethod.getAnnotation(Timed.class);
return (timedAnnotation != null && timedAnnotation.millis() > 0 ? timedAnnotation.millis() : 0);
}
/**
* Wraps the {@link Statement} returned by the parent implementation with a
* {@link RunBeforeTestMethodCallbacks} statement, thus preserving the
* default functionality but adding support for the Spring TestContext
* Framework.
* @see RunBeforeTestMethodCallbacks
*/
@Override
protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement);
return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(),
getTestContextManager());
}
/**
* Wraps the {@link Statement} returned by the parent implementation with a
* {@link RunAfterTestMethodCallbacks} statement, thus preserving the
* default functionality but adding support for the Spring TestContext
* Framework.
* @see RunAfterTestMethodCallbacks
*/
@Override
protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) {
Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement);
return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(),
getTestContextManager());
}
/**
* Supports Spring's {@link Repeat &#064;Repeat} annotation by returning a
* {@link SpringRepeat} statement initialized with the configured repeat
* count or <code>1</code> if no repeat count is configured.
* @see SpringRepeat
*/
protected Statement withPotentialRepeat(FrameworkMethod frameworkMethod, Object testInstance, Statement next) {
Repeat repeatAnnotation = frameworkMethod.getAnnotation(Repeat.class);
int repeat = (repeatAnnotation != null ? repeatAnnotation.value() : 1);
return new SpringRepeat(next, frameworkMethod.getMethod(), repeat);
}
}

View File

@@ -0,0 +1,7 @@
/**
* <p>Support classes for ApplicationContext-based and transactional
* tests run with JUnit 4.5+ and the <em>Spring TestContext Framework</em>.</p>
*/
package org.springframework.test.context.junit4;

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2002-2011 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.context.junit4.statements;
import java.util.ArrayList;
import java.util.List;
import org.junit.internal.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
import org.springframework.test.context.TestContextManager;
/**
* <code>RunAfterTestClassCallbacks</code> is a custom JUnit 4.5+
* {@link Statement} which allows the <em>Spring TestContext Framework</em> to
* be plugged into the JUnit execution chain by calling
* {@link TestContextManager#afterTestClass() afterTestClass()} on the supplied
* {@link TestContextManager}.
*
* @see #evaluate()
* @see RunBeforeTestMethodCallbacks
* @author Sam Brannen
* @since 3.0
*/
@SuppressWarnings("deprecation")
public class RunAfterTestClassCallbacks extends Statement {
private final Statement next;
private final TestContextManager testContextManager;
/**
* Constructs a new <code>RunAfterTestClassCallbacks</code> statement.
*
* @param next the next <code>Statement</code> in the execution chain
* @param testContextManager the TestContextManager upon which to call
* <code>afterTestClass()</code>
*/
public RunAfterTestClassCallbacks(Statement next, TestContextManager testContextManager) {
this.next = next;
this.testContextManager = testContextManager;
}
/**
* Invokes the next {@link Statement} in the execution chain (typically an
* instance of {@link org.junit.internal.runners.statements.RunAfters
* RunAfters}), catching any exceptions thrown, and then calls
* {@link TestContextManager#afterTestClass()}. If the call to
* <code>afterTestClass()</code> throws an exception, it will also be
* tracked. Multiple exceptions will be combined into a
* {@link MultipleFailureException}.
*/
@Override
public void evaluate() throws Throwable {
List<Throwable> errors = new ArrayList<Throwable>();
try {
this.next.evaluate();
}
catch (Throwable e) {
errors.add(e);
}
try {
this.testContextManager.afterTestClass();
}
catch (Exception e) {
errors.add(e);
}
if (errors.isEmpty()) {
return;
}
if (errors.size() == 1) {
throw errors.get(0);
}
throw new MultipleFailureException(errors);
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2002-2011 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.context.junit4.statements;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.junit.internal.runners.model.MultipleFailureException;
import org.junit.runners.model.Statement;
import org.springframework.test.context.TestContextManager;
/**
* <code>RunAfterTestMethodCallbacks</code> is a custom JUnit 4.5+
* {@link Statement} which allows the <em>Spring TestContext Framework</em> to
* be plugged into the JUnit execution chain by calling
* {@link TestContextManager#afterTestMethod(Object, Method) afterTestMethod()}
* on the supplied {@link TestContextManager}.
*
* @see #evaluate()
* @see RunBeforeTestMethodCallbacks
* @author Sam Brannen
* @since 3.0
*/
@SuppressWarnings("deprecation")
public class RunAfterTestMethodCallbacks extends Statement {
private final Statement next;
private final Object testInstance;
private final Method testMethod;
private final TestContextManager testContextManager;
/**
* Constructs a new <code>RunAfterTestMethodCallbacks</code> statement.
*
* @param next the next <code>Statement</code> in the execution chain
* @param testInstance the current test instance (never <code>null</code>)
* @param testMethod the test method which has just been executed on the
* test instance
* @param testContextManager the TestContextManager upon which to call
* <code>afterTestMethod()</code>
*/
public RunAfterTestMethodCallbacks(Statement next, Object testInstance, Method testMethod,
TestContextManager testContextManager) {
this.next = next;
this.testInstance = testInstance;
this.testMethod = testMethod;
this.testContextManager = testContextManager;
}
/**
* Invokes the next {@link Statement} in the execution chain (typically an
* instance of {@link org.junit.internal.runners.statements.RunAfters
* RunAfters}), catching any exceptions thrown, and then calls
* {@link TestContextManager#afterTestMethod(Object, Method)} with the first
* caught exception (if any). If the call to <code>afterTestMethod()</code>
* throws an exception, it will also be tracked. Multiple exceptions will be
* combined into a {@link MultipleFailureException}.
*/
@Override
public void evaluate() throws Throwable {
Throwable testException = null;
List<Throwable> errors = new ArrayList<Throwable>();
try {
this.next.evaluate();
}
catch (Throwable e) {
testException = e;
errors.add(e);
}
try {
this.testContextManager.afterTestMethod(this.testInstance, this.testMethod, testException);
}
catch (Exception e) {
errors.add(e);
}
if (errors.isEmpty()) {
return;
}
if (errors.size() == 1) {
throw errors.get(0);
}
throw new MultipleFailureException(errors);
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2002-2009 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.context.junit4.statements;
import org.junit.runners.model.Statement;
import org.springframework.test.context.TestContextManager;
/**
* <code>RunBeforeTestClassCallbacks</code> is a custom JUnit 4.5+
* {@link Statement} which allows the <em>Spring TestContext Framework</em> to
* be plugged into the JUnit execution chain by calling
* {@link TestContextManager#beforeTestClass() beforeTestClass()} on the
* supplied {@link TestContextManager}.
*
* @see #evaluate()
* @see RunAfterTestMethodCallbacks
* @author Sam Brannen
* @since 3.0
*/
public class RunBeforeTestClassCallbacks extends Statement {
private final Statement next;
private final TestContextManager testContextManager;
/**
* Constructs a new <code>RunBeforeTestClassCallbacks</code> statement.
*
* @param next the next <code>Statement</code> in the execution chain
* @param testContextManager the TestContextManager upon which to call
* <code>beforeTestClass()</code>
*/
public RunBeforeTestClassCallbacks(Statement next, TestContextManager testContextManager) {
this.next = next;
this.testContextManager = testContextManager;
}
/**
* Calls {@link TestContextManager#beforeTestClass()} and then invokes the
* next {@link Statement} in the execution chain (typically an instance of
* {@link org.junit.internal.runners.statements.RunBefores RunBefores}).
*/
@Override
public void evaluate() throws Throwable {
this.testContextManager.beforeTestClass();
this.next.evaluate();
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2002-2009 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.context.junit4.statements;
import java.lang.reflect.Method;
import org.junit.runners.model.Statement;
import org.springframework.test.context.TestContextManager;
/**
* <code>RunBeforeTestMethodCallbacks</code> is a custom JUnit 4.5+
* {@link Statement} which allows the <em>Spring TestContext Framework</em> to
* be plugged into the JUnit execution chain by calling
* {@link TestContextManager#beforeTestMethod(Object, Method)
* beforeTestMethod()} on the supplied {@link TestContextManager}.
*
* @see #evaluate()
* @see RunAfterTestMethodCallbacks
* @author Sam Brannen
* @since 3.0
*/
public class RunBeforeTestMethodCallbacks extends Statement {
private final Statement next;
private final Object testInstance;
private final Method testMethod;
private final TestContextManager testContextManager;
/**
* Constructs a new <code>RunBeforeTestMethodCallbacks</code> statement.
*
* @param next the next <code>Statement</code> in the execution chain
* @param testInstance the current test instance (never <code>null</code>)
* @param testMethod the test method which is about to be executed on the
* test instance
* @param testContextManager the TestContextManager upon which to call
* <code>beforeTestMethod()</code>
*/
public RunBeforeTestMethodCallbacks(Statement next, Object testInstance, Method testMethod,
TestContextManager testContextManager) {
this.next = next;
this.testInstance = testInstance;
this.testMethod = testMethod;
this.testContextManager = testContextManager;
}
/**
* Calls {@link TestContextManager#beforeTestMethod(Object, Method)} and
* then invokes the next {@link Statement} in the execution chain (typically
* an instance of {@link org.junit.internal.runners.statements.RunBefores
* RunBefores}).
*/
@Override
public void evaluate() throws Throwable {
this.testContextManager.beforeTestMethod(this.testInstance, this.testMethod);
this.next.evaluate();
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2009 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.context.junit4.statements;
import java.util.concurrent.TimeoutException;
import org.junit.runners.model.Statement;
import org.springframework.test.annotation.Timed;
/**
* <code>SpringFailOnTimeout</code> is a custom JUnit 4.5+ {@link Statement}
* which adds support for Spring's {@link Timed @Timed} annotation by throwing
* an exception if the next statement in the execution chain takes more than the
* specified number of milliseconds.
*
* @see #evaluate()
* @author Sam Brannen
* @since 3.0
*/
public class SpringFailOnTimeout extends Statement {
private final Statement next;
private final long timeout;
/**
* Constructs a new <code>SpringFailOnTimeout</code> statement.
*
* @param next the next <code>Statement</code> in the execution chain
* @param timeout the configured <code>timeout</code> for the current test
* @see Timed#millis()
*/
public SpringFailOnTimeout(Statement next, long timeout) {
this.next = next;
this.timeout = timeout;
}
/**
* Invokes the next {@link Statement statement} in the execution chain
* (typically an instance of
* {@link org.junit.internal.runners.statements.InvokeMethod InvokeMethod}
* or {@link org.junit.internal.runners.statements.ExpectException
* ExpectException}) and throws an exception if the next
* <code>statement</code> takes more than the specified <code>timeout</code>
* .
*/
@Override
public void evaluate() throws Throwable {
long startTime = System.currentTimeMillis();
try {
this.next.evaluate();
}
finally {
long elapsed = System.currentTimeMillis() - startTime;
if (elapsed > this.timeout) {
throw new TimeoutException(String.format("Test took %s ms; limit was %s ms.", elapsed, this.timeout));
}
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2002-2009 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.context.junit4.statements;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.runners.model.Statement;
import org.springframework.test.annotation.Repeat;
import org.springframework.util.ClassUtils;
/**
* <code>SpringRepeat</code> is a custom JUnit 4.5+ {@link Statement} which adds
* support for Spring's {@link Repeat &#064;Repeat} annotation by repeating the
* test for the specified number of times.
*
* @see #evaluate()
* @author Sam Brannen
* @since 3.0
*/
public class SpringRepeat extends Statement {
protected static final Log logger = LogFactory.getLog(SpringRepeat.class);
private final Statement next;
private final Method testMethod;
private final int repeat;
/**
* Constructs a new <code>SpringRepeat</code> statement.
*
* @param next the next <code>Statement</code> in the execution chain
* @param testMethod the current test method
* @param repeat the configured repeat count for the current test method
* @see Repeat#value()
*/
public SpringRepeat(Statement next, Method testMethod, int repeat) {
this.next = next;
this.testMethod = testMethod;
this.repeat = Math.max(1, repeat);
}
/**
* Invokes the next {@link Statement statement} in the execution chain for
* the specified repeat count.
*/
@Override
public void evaluate() throws Throwable {
for (int i = 0; i < this.repeat; i++) {
if (this.repeat > 1 && logger.isInfoEnabled()) {
logger.info(String.format("Repetition %d of test %s#%s()", (i + 1),
ClassUtils.getShortName(this.testMethod.getDeclaringClass()), this.testMethod.getName()));
}
this.next.evaluate();
}
}
}

View File

@@ -0,0 +1,8 @@
/**
*
* <p>JUnit 4.5 based <code>statements</code> used in the <em>Spring TestContext Framework</em>.</p>
*
*/
package org.springframework.test.context.junit4.statements;

View File

@@ -0,0 +1,17 @@
/**
* <p>This package contains the <em>Spring TestContext Framework</em>
* which provides annotation-driven unit and integration testing support
* that is agnostic of the actual testing framework in use. The same
* techniques and annotation-based configuration used in, for example, a
* JUnit 3.8 environment can also be applied to tests written with JUnit
* 4.5+, TestNG, etc.</p>
*
* <p>In addition to providing generic and extensible testing
* infrastructure, the Spring TestContext Framework provides out-of-the-box
* support for Spring-specific integration testing functionality such as
* context management and caching, dependency injection of test fixtures,
* and transactional test management with default rollback semantics.</p>
*/
package org.springframework.test.context;

View File

@@ -0,0 +1,218 @@
/*
* Copyright 2002-2011 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.context.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* Abstract application context loader that provides a basis for all concrete
* implementations of the {@link ContextLoader} SPI. Provides a
* <em>Template Method</em> based approach for {@link #processLocations processing}
* resource locations.
*
* <p>As of Spring 3.1, <code>AbstractContextLoader</code> also provides a basis
* for all concrete implementations of the {@link SmartContextLoader} SPI. For
* backwards compatibility with the {@code ContextLoader} SPI,
* {@link #processContextConfiguration()} delegates to {@link #processLocations()}.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see #generateDefaultLocations
* @see #modifyLocations
*/
public abstract class AbstractContextLoader implements SmartContextLoader {
private static final Log logger = LogFactory.getLog(AbstractContextLoader.class);
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String SLASH = "/";
// --- SmartContextLoader -----------------------------------------------
/**
* For backwards compatibility with the {@link ContextLoader} SPI, the
* default implementation simply delegates to {@link #processLocations()},
* passing it the {@link ContextConfigurationAttributes#getDeclaringClass()
* declaring class} and {@link ContextConfigurationAttributes#getLocations()
* resource locations} retrieved from the supplied
* {@link ContextConfigurationAttributes configuration attributes}. The
* processed locations are then
* {@link ContextConfigurationAttributes#setLocations(String[]) set} in
* the supplied configuration attributes.
* <p>Can be overridden in subclasses &mdash; for example, to process
* configuration classes instead of resource locations.
* @since 3.1
* @see #processLocations()
*/
public void processContextConfiguration(ContextConfigurationAttributes configAttributes) {
String[] processedLocations = processLocations(configAttributes.getDeclaringClass(),
configAttributes.getLocations());
configAttributes.setLocations(processedLocations);
}
// --- ContextLoader -------------------------------------------------------
/**
* If the supplied <code>locations</code> are <code>null</code> or
* <em>empty</em> and {@link #isGenerateDefaultLocations()} returns
* <code>true</code>, default locations will be
* {@link #generateDefaultLocations(Class) generated} for the specified
* {@link Class class} and the configured
* {@link #getResourceSuffix() resource suffix}; otherwise, the supplied
* <code>locations</code> will be {@link #modifyLocations modified} if
* necessary and returned.
* @param clazz the class with which the locations are associated: to be
* used when generating default locations
* @param locations the unmodified locations to use for loading the
* application context (can be <code>null</code> or empty)
* @return a processed array of application context resource locations
* @since 2.5
* @see #isGenerateDefaultLocations()
* @see #generateDefaultLocations()
* @see #modifyLocations()
* @see org.springframework.test.context.ContextLoader#processLocations()
* @see #processContextConfiguration()
*/
public final String[] processLocations(Class<?> clazz, String... locations) {
return (ObjectUtils.isEmpty(locations) && isGenerateDefaultLocations()) ? generateDefaultLocations(clazz)
: modifyLocations(clazz, locations);
}
/**
* Generate the default classpath resource locations array based on the
* supplied class.
* <p>For example, if the supplied class is <code>com.example.MyTest</code>,
* the generated locations will contain a single string with a value of
* &quot;classpath:/com/example/MyTest<code>&lt;suffix&gt;</code>&quot;,
* where <code>&lt;suffix&gt;</code> is the value of the
* {@link #getResourceSuffix() resource suffix} string.
* <p>As of Spring 3.1, the implementation of this method adheres to the
* contract defined in the {@link SmartContextLoader} SPI. Specifically,
* this method will <em>preemptively</em> verify that the generated default
* location actually exists. If it does not exist, this method will log a
* warning and return an empty array.
* <p>Subclasses can override this method to implement a different
* <em>default location generation</em> strategy.
* @param clazz the class for which the default locations are to be generated
* @return an array of default application context resource locations
* @since 2.5
* @see #getResourceSuffix()
*/
protected String[] generateDefaultLocations(Class<?> clazz) {
Assert.notNull(clazz, "Class must not be null");
String suffix = getResourceSuffix();
Assert.hasText(suffix, "Resource suffix must not be empty");
String resourcePath = SLASH + ClassUtils.convertClassNameToResourcePath(clazz.getName()) + suffix;
String prefixedResourcePath = ResourceUtils.CLASSPATH_URL_PREFIX + resourcePath;
ClassPathResource classPathResource = new ClassPathResource(resourcePath, clazz);
if (classPathResource.exists()) {
if (logger.isInfoEnabled()) {
logger.info(String.format("Detected default resource location \"%s\" for test class [%s].",
prefixedResourcePath, clazz.getName()));
}
return new String[] { prefixedResourcePath };
}
// else
if (logger.isInfoEnabled()) {
logger.info(String.format("Could not detect default resource locations for test class [%s]: "
+ "%s does not exist.", clazz.getName(), classPathResource));
}
return EMPTY_STRING_ARRAY;
}
/**
* Generate a modified version of the supplied locations array and return it.
* <p>A plain path &mdash; for example, &quot;context.xml&quot; &mdash; will
* be treated as a classpath resource that is relative to the package in which
* the specified class is defined. A path starting with a slash is treated
* as an absolute classpath location, for example:
* &quot;/org/springframework/whatever/foo.xml&quot;. A path which
* references a URL (e.g., a path prefixed with
* {@link ResourceUtils#CLASSPATH_URL_PREFIX classpath:},
* {@link ResourceUtils#FILE_URL_PREFIX file:}, <code>http:</code>,
* etc.) will be added to the results unchanged.
* <p>Subclasses can override this method to implement a different
* <em>location modification</em> strategy.
* @param clazz the class with which the locations are associated
* @param locations the resource locations to be modified
* @return an array of modified application context resource locations
* @since 2.5
*/
protected String[] modifyLocations(Class<?> clazz, String... locations) {
String[] modifiedLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
String path = locations[i];
if (path.startsWith(SLASH)) {
modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + path;
}
else if (!ResourcePatternUtils.isUrl(path)) {
modifiedLocations[i] = ResourceUtils.CLASSPATH_URL_PREFIX + SLASH
+ StringUtils.cleanPath(ClassUtils.classPackageAsResourcePath(clazz) + SLASH + path);
}
else {
modifiedLocations[i] = StringUtils.cleanPath(path);
}
}
return modifiedLocations;
}
/**
* Determine whether or not <em>default</em> resource locations should be
* generated if the <code>locations</code> provided to
* {@link #processLocations()} are <code>null</code> or empty.
* <p>As of Spring 3.1, the semantics of this method have been overloaded
* to include detection of either default resource locations or default
* configuration classes. Consequently, this method can also be used to
* determine whether or not <em>default</em> configuration classes should be
* detected if the <code>classes</code> present in the
* {@link ContextConfigurationAttributes configuration attributes} supplied
* to {@link #processContextConfiguration()} are <code>null</code> or empty.
* <p>Can be overridden by subclasses to change the default behavior.
* @return always <code>true</code> by default
* @since 2.5
*/
protected boolean isGenerateDefaultLocations() {
return true;
}
/**
* Get the suffix to append to {@link ApplicationContext} resource
* locations when generating default locations.
* <p>Must be implemented by subclasses.
* @return the resource suffix; should not be <code>null</code> or empty
* @since 2.5
* @see #generateDefaultLocations()
*/
protected abstract String getResourceSuffix();
}

View File

@@ -0,0 +1,235 @@
/*
* Copyright 2002-2011 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.context.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.StringUtils;
/**
* Abstract, generic extension of {@link AbstractContextLoader} that loads a
* {@link GenericApplicationContext}.
*
* <ul>
* <li>If instances of concrete subclasses are invoked via the
* {@link org.springframework.test.context.ContextLoader ContextLoader} SPI, the
* context will be loaded from the <em>locations</em> provided to
* {@link #loadContext(String...)}.</li>
* <li>If instances of concrete subclasses are invoked via the
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
* SPI, the context will be loaded from the {@link MergedContextConfiguration}
* provided to {@link #loadContext(MergedContextConfiguration)}. In such cases, a
* <code>SmartContextLoader</code> will decide whether to load the context from
* <em>locations</em> or
* {@link org.springframework.context.annotation.Configuration configuration classes}.</li>
* </ul>
*
* <p>Concrete subclasses must provide an appropriate implementation of
* {@link #createBeanDefinitionReader createBeanDefinitionReader()},
* potentially overriding {@link #loadBeanDefinitions loadBeanDefinitions()}
* as well.
*
* @author Sam Brannen
* @author Juergen Hoeller
* @since 2.5
* @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
*/
public abstract class AbstractGenericContextLoader extends AbstractContextLoader {
protected static final Log logger = LogFactory.getLog(AbstractGenericContextLoader.class);
/**
* Load a Spring ApplicationContext from the supplied {@link MergedContextConfiguration}.
* <p>Implementation details:
* <ul>
* <li>Creates a {@link GenericApplicationContext} instance.</li>
* <li>Sets the <em>active bean definition profiles</em> from the supplied
* <code>MergedContextConfiguration</code> in the
* {@link org.springframework.core.env.Environment Environment} of the context.</li>
* <li>Calls {@link #prepareContext()} to allow for customizing the context
* before bean definitions are loaded.</li>
* <li>Calls {@link #customizeBeanFactory()} to allow for customizing the
* context's <code>DefaultListableBeanFactory</code>.</li>
* <li>Delegates to {@link #loadBeanDefinitions()} to populate the context
* from the configuration locations or classes in the supplied
* <code>MergedContextConfiguration</code>.</li>
* <li>Delegates to {@link AnnotationConfigUtils} for
* {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering}
* annotation configuration processors.</li>
* <li>Calls {@link #customizeContext()} to allow for customizing the context
* before it is refreshed.</li>
* <li>{@link ConfigurableApplicationContext#refresh Refreshes} the
* context and registers a JVM shutdown hook for it.</li>
* </ul>
* @return a new application context
* @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
* @see GenericApplicationContext
* @since 3.1
*/
public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
mergedConfig));
}
GenericApplicationContext context = new GenericApplicationContext();
context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
prepareContext(context);
customizeBeanFactory(context.getDefaultListableBeanFactory());
loadBeanDefinitions(context, mergedConfig);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context);
context.refresh();
context.registerShutdownHook();
return context;
}
/**
* Load a Spring ApplicationContext from the supplied <code>locations</code>.
* <p>Implementation details:
* <ul>
* <li>Creates a {@link GenericApplicationContext} instance.</li>
* <li>Calls {@link #prepareContext()} to allow for customizing the context
* before bean definitions are loaded.</li>
* <li>Calls {@link #customizeBeanFactory()} to allow for customizing the
* context's <code>DefaultListableBeanFactory</code>.</li>
* <li>Delegates to {@link #createBeanDefinitionReader()} to create a
* {@link BeanDefinitionReader} which is then used to populate the context
* from the specified config locations.</li>
* <li>Delegates to {@link AnnotationConfigUtils} for
* {@link AnnotationConfigUtils#registerAnnotationConfigProcessors registering}
* annotation configuration processors.</li>
* <li>Calls {@link #customizeContext()} to allow for customizing the context
* before it is refreshed.</li>
* <li>{@link ConfigurableApplicationContext#refresh Refreshes} the
* context and registers a JVM shutdown hook for it.</li>
* </ul>
* <p><b>Note</b>: this method does not provide a means to set active bean definition
* profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration)}
* for an alternative.
* @return a new application context
* @see org.springframework.test.context.ContextLoader#loadContext
* @see GenericApplicationContext
* @see #loadContext(MergedContextConfiguration)
* @since 2.5
*/
public final ConfigurableApplicationContext loadContext(String... locations) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Loading ApplicationContext for locations [%s].",
StringUtils.arrayToCommaDelimitedString(locations)));
}
GenericApplicationContext context = new GenericApplicationContext();
prepareContext(context);
customizeBeanFactory(context.getDefaultListableBeanFactory());
createBeanDefinitionReader(context).loadBeanDefinitions(locations);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context);
context.refresh();
context.registerShutdownHook();
return context;
}
/**
* Prepare the {@link GenericApplicationContext} created by this <code>ContextLoader</code>.
* Called <i>before</i> bean definitions are read.
* <p>The default implementation is empty. Can be overridden in subclasses to
* customize <code>GenericApplicationContext</code>'s standard settings.
* @param context the context that should be prepared
* @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
* @see GenericApplicationContext#setAllowBeanDefinitionOverriding
* @see GenericApplicationContext#setResourceLoader
* @see GenericApplicationContext#setId
* @since 2.5
*/
protected void prepareContext(GenericApplicationContext context) {
}
/**
* Customize the internal bean factory of the ApplicationContext created by
* this <code>ContextLoader</code>.
* <p>The default implementation is empty but can be overridden in subclasses
* to customize <code>DefaultListableBeanFactory</code>'s standard settings.
* @param beanFactory the bean factory created by this <code>ContextLoader</code>
* @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
* @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
* @see DefaultListableBeanFactory#setAllowEagerClassLoading
* @see DefaultListableBeanFactory#setAllowCircularReferences
* @see DefaultListableBeanFactory#setAllowRawInjectionDespiteWrapping
* @since 2.5
*/
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
}
/**
* Load bean definitions into the supplied {@link GenericApplicationContext context}
* from the configuration locations or classes in the supplied
* <code>MergedContextConfiguration</code>.</li>
* <p>The default implementation delegates to the {@link BeanDefinitionReader}
* returned by {@link #createBeanDefinitionReader()} to
* {@link BeanDefinitionReader#loadBeanDefinitions(String) load} the
* bean definitions.
* <p>Subclasses must provide an appropriate implementation of
* {@link #createBeanDefinitionReader()}. Alternatively subclasses may
* provide a <em>no-op</em> implementation of {@code createBeanDefinitionReader()}
* and override this method to provide a custom strategy for loading or
* registering bean definitions.
* @param context the context into which the bean definitions should be loaded
* @param mergedConfig the merged context configuration
* @see #loadContext(MergedContextConfiguration)
* @since 3.1
*/
protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) {
createBeanDefinitionReader(context).loadBeanDefinitions(mergedConfig.getLocations());
}
/**
* Factory method for creating a new {@link BeanDefinitionReader} for loading
* bean definitions into the supplied {@link GenericApplicationContext context}.
* @param context the context for which the <code>BeanDefinitionReader</code>
* should be created
* @return a <code>BeanDefinitionReader</code> for the supplied context
* @see #loadContext(String...)
* @see #loadBeanDefinitions
* @see BeanDefinitionReader
* @since 2.5
*/
protected abstract BeanDefinitionReader createBeanDefinitionReader(GenericApplicationContext context);
/**
* Customize the {@link GenericApplicationContext} created by this
* <code>ContextLoader</code> <i>after</i> bean definitions have been
* loaded into the context but <i>before</i> the context is refreshed.
* <p>The default implementation is empty but can be overridden in subclasses
* to customize the application context.
* @param context the newly created application context
* @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
* @since 2.5
*/
protected void customizeContext(GenericApplicationContext context) {
}
}

Some files were not shown because too many files have changed in this diff Show More