();
+
+
+ /**
+ * Construct a new JndiTemplate that will always return given objects
+ * for given names. To be populated through addObject 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;
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java b/org.springframework.context/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java
new file mode 100644
index 00000000000..b6489e42029
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java
@@ -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.
+ *
+ * This class is not intended for direct usage by applications, although it
+ * can be used for example to override JndiTemplate's createInitialContext
+ * 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 boundObjects;
+
+ private final Hashtable environment = new Hashtable();
+
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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 boundObjects, Hashtable env) {
+ this.root = root;
+ this.boundObjects = boundObjects;
+ if (env != null) {
+ this.environment.putAll(env);
+ }
+ }
+
+
+ // Actual implementations of Context methods follow
+
+ public NamingEnumeration list(String root) throws NamingException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Listing name/class pairs under [" + root + "]");
+ }
+ return new NameClassPairEnumeration(this, root);
+ }
+
+ public NamingEnumeration 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.
+ * 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 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 list(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public NamingEnumeration 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 implements NamingEnumeration {
+
+ private Iterator iterator;
+
+ private AbstractNamingEnumeration(SimpleNamingContext context, String proot) throws NamingException {
+ if (!"".equals(proot) && !proot.endsWith("/")) {
+ proot = proot + "/";
+ }
+ String root = context.root + proot;
+ Map contents = new HashMap();
+ 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 {
+
+ 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 {
+
+ private BindingEnumeration(SimpleNamingContext context, String root) throws NamingException {
+ super(context, root);
+ }
+
+ protected Binding createObject(String strippedName, Object obj) {
+ return new Binding(strippedName, obj);
+ }
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java b/org.springframework.context/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
new file mode 100644
index 00000000000..130de79de5e
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
@@ -0,0 +1,234 @@
+/*
+ * 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.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.
+ *
+ * Mainly targeted at test environments, where each test case can
+ * configure JNDI appropriately, so that new InitialContext()
+ * 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.
+ *
+ *
There are various choices for DataSource implementations:
+ *
+ * - SingleConnectionDataSource (using the same Connection for all getConnection calls);
+ *
- DriverManagerDataSource (creating a new Connection on each getConnection call);
+ *
- Apache's Jakarta Commons DBCP offers BasicDataSource (a real pool).
+ *
+ *
+ * Typical usage in bootstrap code:
+ *
+ *
+ * SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
+ * DataSource ds = new DriverManagerDataSource(...);
+ * builder.bind("java:comp/env/jdbc/myds", ds);
+ * builder.activate();
+ *
+ * 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:
+ *
+ *
+ * SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
+ * DataSource ds = new DriverManagerDataSource(...);
+ * builder.bind("java:comp/env/jdbc/myds", ds);
+ *
+ * Note that you should not call activate() on a builder from
+ * this factory method, as there will already be an activated one in any case.
+ *
+ * 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 null 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.
+ *
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 assigment to the activated variable.
+ builder.activate();
+ }
+ return activated;
+ }
+
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final Hashtable boundObjects = new Hashtable();
+
+
+ /**
+ * Register the context builder by registering it with the JNDI NamingManager.
+ * Note that once this has been done, new InitialContext() will always
+ * return a context from this factory. Use the emptyActivatedContextBuilder()
+ * 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.
+ *
Call activate() again in order to expose this contexz 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() {
+ public Context getInitialContext(Hashtable environment) {
+ return new SimpleNamingContext("", boundObjects, environment);
+ }
+ };
+ }
+
+}
diff --git a/org.springframework.context/src/test/java/org/springframework/mock/jndi/package.html b/org.springframework.context/src/test/java/org/springframework/mock/jndi/package.html
new file mode 100644
index 00000000000..141ee8db0fa
--- /dev/null
+++ b/org.springframework.context/src/test/java/org/springframework/mock/jndi/package.html
@@ -0,0 +1,12 @@
+
+
+
+The simplest implementation of the JNDI SPI that could possibly work.
+
+Useful for setting up a simple JNDI environment for test suites
+or standalone applications. If e.g. JDBC DataSources get bound to the
+same JNDI names as within a J2EE container, both application code and
+configuration can me reused without changes.
+
+
+
diff --git a/org.springframework.testsuite/src/test/java/example/aspects/PerTargetAspect.java b/org.springframework.testsuite/src/test/java/example/aspects/PerTargetAspect.java
new file mode 100644
index 00000000000..4244c389493
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/aspects/PerTargetAspect.java
@@ -0,0 +1,35 @@
+/**
+ *
+ */
+package example.aspects;
+
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.core.Ordered;
+
+@Aspect("pertarget(execution(* *.getSpouse()))")
+public class PerTargetAspect implements Ordered {
+
+ public int count;
+
+ private int order = Ordered.LOWEST_PRECEDENCE;
+
+ @Around("execution(int *.getAge())")
+ public int returnCountAsAge() {
+ return count++;
+ }
+
+ @Before("execution(void *.set*(int))")
+ public void countSetter() {
+ ++count;
+ }
+
+ public int getOrder() {
+ return this.order;
+ }
+
+ public void setOrder(int order) {
+ this.order = order;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.testsuite/src/test/java/example/aspects/PerThisAspect.java b/org.springframework.testsuite/src/test/java/example/aspects/PerThisAspect.java
new file mode 100644
index 00000000000..9f95aaa018b
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/aspects/PerThisAspect.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2005 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example.aspects;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+
+@Aspect("perthis(execution(* getAge()))")
+public class PerThisAspect {
+
+ private int invocations = 0;
+
+ public int getInvocations() {
+ return this.invocations;
+ }
+
+ @Around("execution(* getAge())")
+ public int changeAge(ProceedingJoinPoint pjp) throws Throwable {
+ return invocations++;
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/aspects/TwoAdviceAspect.java b/org.springframework.testsuite/src/test/java/example/aspects/TwoAdviceAspect.java
new file mode 100644
index 00000000000..e69d7d99e57
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/aspects/TwoAdviceAspect.java
@@ -0,0 +1,24 @@
+/**
+ *
+ */
+package example.aspects;
+
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+
+@Aspect
+public class TwoAdviceAspect {
+ private int totalCalls;
+
+ @Around("execution(* getAge())")
+ public int returnCallCount(ProceedingJoinPoint pjp) throws Exception {
+ return totalCalls;
+ }
+
+ @Before("execution(* setAge(int)) && args(newAge)")
+ public void countSet(int newAge) throws Exception {
+ ++totalCalls;
+ }
+}
\ No newline at end of file
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/AutowiredQualifierFooService.java b/org.springframework.testsuite/src/test/java/example/scannable/AutowiredQualifierFooService.java
new file mode 100644
index 00000000000..6db0d795543
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/AutowiredQualifierFooService.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package example.scannable;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+/**
+ * @author Mark Fisher
+ */
+public class AutowiredQualifierFooService implements FooService {
+
+ @Autowired
+ @Qualifier("testing")
+ private FooDao fooDao;
+
+ private boolean initCalled = false;
+
+ @PostConstruct
+ private void init() {
+ if (this.initCalled) {
+ throw new IllegalStateException("Init already called");
+ }
+ this.initCalled = true;
+ }
+
+ public String foo(int id) {
+ return this.fooDao.findFoo(id);
+ }
+
+ public boolean isInitCalled() {
+ return this.initCalled;
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/CustomComponent.java b/org.springframework.testsuite/src/test/java/example/scannable/CustomComponent.java
new file mode 100644
index 00000000000..2f6ef7f42a9
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/CustomComponent.java
@@ -0,0 +1,33 @@
+/*
+ * 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 example.scannable;
+
+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;
+
+/**
+ * @author Mark Fisher
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface CustomComponent {
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/CustomStereotype.java b/org.springframework.testsuite/src/test/java/example/scannable/CustomStereotype.java
new file mode 100644
index 00000000000..656ad49bd8f
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/CustomStereotype.java
@@ -0,0 +1,36 @@
+/*
+ * 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 example.scannable;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Juergen Hoeller
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Component
+public @interface CustomStereotype {
+
+ String value() default "thoreau";
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/DefaultNamedComponent.java b/org.springframework.testsuite/src/test/java/example/scannable/DefaultNamedComponent.java
new file mode 100644
index 00000000000..8ce68ee3d41
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/DefaultNamedComponent.java
@@ -0,0 +1,26 @@
+/*
+ * 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 example.scannable;
+
+
+/**
+ * @author Juergen Hoeller
+ */
+@CustomStereotype
+public class DefaultNamedComponent {
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/FooDao.java b/org.springframework.testsuite/src/test/java/example/scannable/FooDao.java
new file mode 100644
index 00000000000..92fe2622d3e
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/FooDao.java
@@ -0,0 +1,26 @@
+/*
+ * 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 example.scannable;
+
+/**
+ * @author Mark Fisher
+ */
+public interface FooDao {
+
+ String findFoo(int id);
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/FooService.java b/org.springframework.testsuite/src/test/java/example/scannable/FooService.java
new file mode 100644
index 00000000000..f5fc4e6bfb0
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/FooService.java
@@ -0,0 +1,29 @@
+/*
+ * 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 example.scannable;
+
+/**
+ * @author Mark Fisher
+ * @author Juergen Hoeller
+ */
+public interface FooService {
+
+ String foo(int id);
+
+ boolean isInitCalled();
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/FooServiceImpl.java b/org.springframework.testsuite/src/test/java/example/scannable/FooServiceImpl.java
new file mode 100644
index 00000000000..1b13d9644db
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/FooServiceImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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 example.scannable;
+
+import java.util.List;
+
+import javax.annotation.PostConstruct;
+
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.MessageSource;
+import org.springframework.context.support.AbstractApplicationContext;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Mark Fisher
+ * @author Juergen Hoeller
+ */
+@Service
+public class FooServiceImpl implements FooService {
+
+ @Autowired private FooDao fooDao;
+
+ @Autowired public BeanFactory beanFactory;
+
+ @Autowired public List listableBeanFactory;
+
+ @Autowired public ResourceLoader resourceLoader;
+
+ @Autowired public ResourcePatternResolver resourcePatternResolver;
+
+ @Autowired public ApplicationEventPublisher eventPublisher;
+
+ @Autowired public MessageSource messageSource;
+
+ @Autowired public ApplicationContext context;
+
+ @Autowired public ConfigurableApplicationContext[] configurableContext;
+
+ @Autowired public AbstractApplicationContext genericContext;
+
+ private boolean initCalled = false;
+
+ @PostConstruct
+ private void init() {
+ if (this.initCalled) {
+ throw new IllegalStateException("Init already called");
+ }
+ this.initCalled = true;
+ }
+
+ public String foo(int id) {
+ return this.fooDao.findFoo(id);
+ }
+
+ public boolean isInitCalled() {
+ return this.initCalled;
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/MessageBean.java b/org.springframework.testsuite/src/test/java/example/scannable/MessageBean.java
new file mode 100644
index 00000000000..a1035f114fb
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/MessageBean.java
@@ -0,0 +1,39 @@
+/*
+ * 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 example.scannable;
+
+/**
+ * @author Mark Fisher
+ */
+@CustomComponent
+public class MessageBean {
+
+ private String message;
+
+ public MessageBean() {
+ this.message = "DEFAULT MESSAGE";
+ }
+
+ public MessageBean(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return this.message;
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/NamedComponent.java b/org.springframework.testsuite/src/test/java/example/scannable/NamedComponent.java
new file mode 100644
index 00000000000..748eef7be8a
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/NamedComponent.java
@@ -0,0 +1,27 @@
+/*
+ * 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 example.scannable;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Mark Fisher
+ */
+@Component("myNamedComponent")
+public class NamedComponent {
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/NamedStubDao.java b/org.springframework.testsuite/src/test/java/example/scannable/NamedStubDao.java
new file mode 100644
index 00000000000..a4eb66b88c6
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/NamedStubDao.java
@@ -0,0 +1,31 @@
+/*
+ * 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 example.scannable;
+
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author Juergen Hoeller
+ */
+@Repository("myNamedDao")
+public class NamedStubDao {
+
+ public String find(int id) {
+ return "bar";
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/ScopedProxyTestBean.java b/org.springframework.testsuite/src/test/java/example/scannable/ScopedProxyTestBean.java
new file mode 100644
index 00000000000..5168b2b66ba
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/ScopedProxyTestBean.java
@@ -0,0 +1,36 @@
+/*
+ * 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 example.scannable;
+
+import org.springframework.context.annotation.Scope;
+
+/**
+ * @author Mark Fisher
+ * @author Juergen Hoeller
+ */
+@Scope("myScope")
+public class ScopedProxyTestBean implements FooService {
+
+ public String foo(int id) {
+ return "bar";
+ }
+
+ public boolean isInitCalled() {
+ return false;
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/ServiceInvocationCounter.java b/org.springframework.testsuite/src/test/java/example/scannable/ServiceInvocationCounter.java
new file mode 100644
index 00000000000..a0aa9da7223
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/ServiceInvocationCounter.java
@@ -0,0 +1,46 @@
+/*
+ * 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 example.scannable;
+
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Mark Fisher
+ */
+@Component
+@Aspect
+public class ServiceInvocationCounter {
+
+ private int useCount;
+
+ @Pointcut("execution(* example.scannable.FooService+.*(..))")
+ public void serviceExecution() {}
+
+ @Before("serviceExecution()")
+ public void countUse() {
+ this.useCount++;
+ }
+
+ public int getCount() {
+ return this.useCount;
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/example/scannable/StubFooDao.java b/org.springframework.testsuite/src/test/java/example/scannable/StubFooDao.java
new file mode 100644
index 00000000000..b4ea5b65d5d
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/example/scannable/StubFooDao.java
@@ -0,0 +1,33 @@
+/*
+ * 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 example.scannable;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @author Mark Fisher
+ */
+@Repository
+@Qualifier("testing")
+public class StubFooDao implements FooDao {
+
+ public String findFoo(int id) {
+ return "bar";
+ }
+
+}
diff --git a/org.springframework.testsuite/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java b/org.springframework.testsuite/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java
index 721565b3233..1ba52a95874 100644
--- a/org.springframework.testsuite/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java
+++ b/org.springframework.testsuite/src/test/java/org/springframework/aop/framework/ProxyFactoryBeanTests.java
@@ -16,8 +16,7 @@
package org.springframework.aop.framework;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.io.FileNotFoundException;
diff --git a/org.springframework.testsuite/src/test/java/org/springframework/mock/web/MockHttpServletRequest.java b/org.springframework.testsuite/src/test/java/org/springframework/mock/web/MockHttpServletRequest.java
new file mode 100644
index 00000000000..527f05c150a
--- /dev/null
+++ b/org.springframework.testsuite/src/test/java/org/springframework/mock/web/MockHttpServletRequest.java
@@ -0,0 +1,849 @@
+/*
+ * 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.web;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+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.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+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.ServletInputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.springframework.util.Assert;
+
+/**
+ * Mock implementation of the {@link javax.servlet.http.HttpServletRequest}
+ * interface. Supports the Servlet 2.4 API level.
+ *
+ * Used for testing the web framework; also useful for testing
+ * application controllers.
+ *
+ * @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 boolean active = true;
+
+
+ //---------------------------------------------------------------------
+ // ServletRequest properties
+ //---------------------------------------------------------------------
+
+ private final Hashtable attributes = new Hashtable();
+
+ private String characterEncoding;
+
+ private byte[] content;
+
+ private String contentType;
+
+ private final Map parameters = new LinkedHashMap(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 Vector locales = new Vector();
+
+ 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;
+
+ /**
+ * The key is the lowercase header name; the value is a {@link HeaderValueHolder} object.
+ */
+ private final Hashtable headers = new Hashtable();
+
+ private String method;
+
+ private String pathInfo;
+
+ private String contextPath = "";
+
+ private String queryString;
+
+ private String remoteUser;
+
+ private final Set userRoles = new HashSet();
+
+ private Principal userPrincipal;
+
+ 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 null)
+ * @param requestURI the request URI (may be null)
+ * @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 null 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 null to use a default MockServletContext)
+ * @param method the request method (may be null)
+ * @param requestURI the request URI (may be null)
+ * @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 getAttributeNames() {
+ checkActive();
+ return this.attributes.keys();
+ }
+
+ public String getCharacterEncoding() {
+ return this.characterEncoding;
+ }
+
+ public void setCharacterEncoding(String characterEncoding) {
+ this.characterEncoding = characterEncoding;
+ }
+
+ 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;
+ }
+
+ 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.
+ * 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.
+ *
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 replacing any
+ * existing values for the provided parameter names. To add without
+ * replacing existing values, use {@link #addParameters(java.util.Map)}.
+ */
+ 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.
+ *
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.
+ *
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 without replacing
+ * any existing values. To replace existing values, use
+ * {@link #setParameters(java.util.Map)}.
+ */
+ 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 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 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 (Locale) this.locales.get(0);
+ }
+
+ public Enumeration getLocales() {
+ return this.locales.elements();
+ }
+
+ 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.
+ *
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.
+ *
Multiple values can only be stored as list of Strings,
+ * following the Servlet spec (see getHeaders accessor).
+ * As alternative to repeated addHeader 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) {
+ 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 (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 String getHeader(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ return (header != null ? header.getValue().toString() : null);
+ }
+
+ public Enumeration getHeaders(String name) {
+ HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name);
+ return Collections.enumeration(header != null ? header.getValues() : Collections.EMPTY_LIST);
+ }
+
+ public Enumeration getHeaderNames() {
+ return this.headers.keys();
+ }
+
+ 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 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);
+ }
+
+ public void setUserPrincipal(Principal userPrincipal) {
+ this.userPrincipal = userPrincipal;
+ }
+
+ public Principal getUserPrincipal() {
+ return this.userPrincipal;
+ }
+
+ public String getRequestedSessionId() {
+ HttpSession session = getSession();
+ return (session != null ? session.getId() : null);
+ }
+
+ 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();
+ }
+
+}
diff --git a/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java b/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java
new file mode 100644
index 00000000000..28670fd4383
--- /dev/null
+++ b/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java
@@ -0,0 +1,82 @@
+/*
+ * 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.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.naming.NamingException;
+
+import org.springframework.core.CollectionFactory;
+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 jndiObjects = new ConcurrentHashMap();
+
+
+ /**
+ * Construct a new JndiTemplate that will always return given objects
+ * for given names. To be populated through addObject 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;
+ }
+
+}
diff --git a/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java b/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java
new file mode 100644
index 00000000000..b6489e42029
--- /dev/null
+++ b/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/SimpleNamingContext.java
@@ -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.
+ *
+ * This class is not intended for direct usage by applications, although it
+ * can be used for example to override JndiTemplate's createInitialContext
+ * 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 boundObjects;
+
+ private final Hashtable environment = new Hashtable();
+
+
+ /**
+ * 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();
+ }
+
+ /**
+ * 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 boundObjects, Hashtable env) {
+ this.root = root;
+ this.boundObjects = boundObjects;
+ if (env != null) {
+ this.environment.putAll(env);
+ }
+ }
+
+
+ // Actual implementations of Context methods follow
+
+ public NamingEnumeration list(String root) throws NamingException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Listing name/class pairs under [" + root + "]");
+ }
+ return new NameClassPairEnumeration(this, root);
+ }
+
+ public NamingEnumeration 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.
+ * 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 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 list(Name name) throws NamingException {
+ throw new OperationNotSupportedException("SimpleNamingContext does not support [javax.naming.Name]");
+ }
+
+ public NamingEnumeration 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 implements NamingEnumeration {
+
+ private Iterator iterator;
+
+ private AbstractNamingEnumeration(SimpleNamingContext context, String proot) throws NamingException {
+ if (!"".equals(proot) && !proot.endsWith("/")) {
+ proot = proot + "/";
+ }
+ String root = context.root + proot;
+ Map contents = new HashMap();
+ 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 {
+
+ 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 {
+
+ private BindingEnumeration(SimpleNamingContext context, String root) throws NamingException {
+ super(context, root);
+ }
+
+ protected Binding createObject(String strippedName, Object obj) {
+ return new Binding(strippedName, obj);
+ }
+ }
+
+}
diff --git a/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java b/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
new file mode 100644
index 00000000000..130de79de5e
--- /dev/null
+++ b/org.springframework.transaction/src/test/java/org/springframework/mock/jndi/SimpleNamingContextBuilder.java
@@ -0,0 +1,234 @@
+/*
+ * 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.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.
+ *
+ * Mainly targeted at test environments, where each test case can
+ * configure JNDI appropriately, so that new InitialContext()
+ * 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.
+ *
+ *
There are various choices for DataSource implementations:
+ *
+ * - SingleConnectionDataSource (using the same Connection for all getConnection calls);
+ *
- DriverManagerDataSource (creating a new Connection on each getConnection call);
+ *
- Apache's Jakarta Commons DBCP offers BasicDataSource (a real pool).
+ *
+ *
+ * Typical usage in bootstrap code:
+ *
+ *
+ * SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
+ * DataSource ds = new DriverManagerDataSource(...);
+ * builder.bind("java:comp/env/jdbc/myds", ds);
+ * builder.activate();
+ *
+ * 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:
+ *
+ *
+ * SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
+ * DataSource ds = new DriverManagerDataSource(...);
+ * builder.bind("java:comp/env/jdbc/myds", ds);
+ *
+ * Note that you should not call activate() on a builder from
+ * this factory method, as there will already be an activated one in any case.
+ *
+ * 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 null 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.
+ *
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 assigment to the activated variable.
+ builder.activate();
+ }
+ return activated;
+ }
+
+
+ private final Log logger = LogFactory.getLog(getClass());
+
+ private final Hashtable boundObjects = new Hashtable();
+
+
+ /**
+ * Register the context builder by registering it with the JNDI NamingManager.
+ * Note that once this has been done, new InitialContext() will always
+ * return a context from this factory. Use the emptyActivatedContextBuilder()
+ * 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.
+ *
Call activate() again in order to expose this contexz 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() {
+ public Context getInitialContext(Hashtable environment) {
+ return new SimpleNamingContext("", boundObjects, environment);
+ }
+ };
+ }
+
+}
diff --git a/org.springframework.transaction/src/test/java/org/springframework/transaction/jta/WebSphereUowTransactionManagerTests.java b/org.springframework.transaction/src/test/java/org/springframework/transaction/jta/WebSphereUowTransactionManagerTests.java
index 8c80f0f0131..91adf3f4cde 100644
--- a/org.springframework.transaction/src/test/java/org/springframework/transaction/jta/WebSphereUowTransactionManagerTests.java
+++ b/org.springframework.transaction/src/test/java/org/springframework/transaction/jta/WebSphereUowTransactionManagerTests.java
@@ -20,12 +20,9 @@ import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
-import com.ibm.wsspi.uow.UOWAction;
-import com.ibm.wsspi.uow.UOWException;
-import com.ibm.wsspi.uow.UOWManager;
import junit.framework.TestCase;
-import org.easymock.MockControl;
+import org.easymock.MockControl;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.mock.jndi.ExpectedLookupTemplate;
import org.springframework.transaction.IllegalTransactionStateException;
@@ -37,6 +34,10 @@ import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronizationManager;
+import com.ibm.wsspi.uow.UOWAction;
+import com.ibm.wsspi.uow.UOWException;
+import com.ibm.wsspi.uow.UOWManager;
+
/**
* @author Juergen Hoeller
*/
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockActionRequest.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockActionRequest.java
new file mode 100644
index 00000000000..e85da537d15
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockActionRequest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.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.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 MockPortletRequest implements ActionRequest {
+
+ private String characterEncoding;
+
+ private byte[] content;
+
+ private String contentType;
+
+
+ /**
+ * Create a new MockActionRequest with a default {@link MockPortalContext}
+ * and a default {@link MockPortletContext}.
+ * @see MockPortalContext
+ * @see MockPortletContext
+ */
+ public MockActionRequest() {
+ super();
+ }
+
+ /**
+ * 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);
+ }
+
+
+ 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 characterEncoding;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public String getContentType() {
+ return contentType;
+ }
+
+ public int getContentLength() {
+ return (this.content != null ? content.length : -1);
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockActionResponse.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockActionResponse.java
new file mode 100644
index 00000000000..e608e81c49f
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockActionResponse.java
@@ -0,0 +1,163 @@
+/*
+ * 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.web.portlet;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+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;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.ActionResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockActionResponse extends MockPortletResponse implements ActionResponse {
+
+ private WindowState windowState;
+
+ private PortletMode portletMode;
+
+ private String redirectedUrl;
+
+ private final Map renderParameters = new LinkedHashMap();
+
+
+ /**
+ * 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");
+ }
+ if (!CollectionUtils.contains(getPortalContext().getSupportedWindowStates(), windowState)) {
+ throw new WindowStateException("WindowState not supported", windowState);
+ }
+ this.windowState = windowState;
+ }
+
+ public WindowState getWindowState() {
+ return windowState;
+ }
+
+ public void setPortletMode(PortletMode portletMode) throws PortletModeException {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set PortletMode after sendRedirect has been called");
+ }
+ if (!CollectionUtils.contains(getPortalContext().getSupportedPortletModes(), portletMode)) {
+ throw new PortletModeException("PortletMode not supported", portletMode);
+ }
+ this.portletMode = portletMode;
+ }
+
+ public PortletMode getPortletMode() {
+ return portletMode;
+ }
+
+ public void sendRedirect(String url) throws IOException {
+ if (this.windowState != null || this.portletMode != null || !this.renderParameters.isEmpty()) {
+ throw new IllegalStateException(
+ "Cannot call sendRedirect after windowState, portletMode, or renderParameters have been set");
+ }
+ Assert.notNull(url, "Redirect URL must not be null");
+ this.redirectedUrl = url;
+ }
+
+ public String getRedirectedUrl() {
+ return redirectedUrl;
+ }
+
+ public void setRenderParameters(Map parameters) {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+ }
+ Assert.notNull(parameters, "Parameters Map must not be null");
+ this.renderParameters.clear();
+ for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Assert.isTrue(entry.getKey() instanceof String, "Key must be of type String");
+ Assert.isTrue(entry.getValue() instanceof String[], "Value must be of type String[]");
+ this.renderParameters.put((String) entry.getKey(), (String[]) entry.getValue());
+ }
+ }
+
+ public void setRenderParameter(String key, String value) {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+ }
+ 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 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 void setRenderParameter(String key, String[] values) {
+ if (this.redirectedUrl != null) {
+ throw new IllegalStateException("Cannot set render parameters after sendRedirect has been called");
+ }
+ 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[] getRenderParameterValues(String key) {
+ Assert.notNull(key, "Parameter key must not be null");
+ return this.renderParameters.get(key);
+ }
+
+ public Iterator getRenderParameterNames() {
+ return this.renderParameters.keySet().iterator();
+ }
+
+ public Map getRenderParameterMap() {
+ return Collections.unmodifiableMap(this.renderParameters);
+ }
+
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java
new file mode 100644
index 00000000000..cc2b2277cff
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.web.portlet;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+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.
+ *
+ * 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
+ * @since 2.0
+ * @see org.springframework.mock.web.MockMultipartFile
+ */
+public class MockMultipartActionRequest extends MockActionRequest implements MultipartActionRequest {
+
+ private final Map multipartFiles = new LinkedHashMap();
+
+
+ /**
+ * 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.put(file.getName(), file);
+ }
+
+ public Iterator getFileNames() {
+ return getFileMap().keySet().iterator();
+ }
+
+ public MultipartFile getFile(String name) {
+ return this.multipartFiles.get(name);
+ }
+
+ public Map getFileMap() {
+ return Collections.unmodifiableMap(this.multipartFiles);
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortalContext.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortalContext.java
new file mode 100644
index 00000000000..1a81847f978
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortalContext.java
@@ -0,0 +1,101 @@
+/*
+ * 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.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 properties = new HashMap();
+
+ private final List portletModes;
+
+ private final List 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(3);
+ this.portletModes.add(PortletMode.VIEW);
+ this.portletModes.add(PortletMode.EDIT);
+ this.portletModes.add(PortletMode.HELP);
+
+ this.windowStates = new ArrayList(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 supportedPortletModes, List supportedWindowStates) {
+ this.portletModes = new ArrayList(supportedPortletModes);
+ this.windowStates = new ArrayList(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 getPropertyNames() {
+ return Collections.enumeration(this.properties.keySet());
+ }
+
+ public Enumeration getSupportedPortletModes() {
+ return Collections.enumeration(this.portletModes);
+ }
+
+ public Enumeration getSupportedWindowStates() {
+ return Collections.enumeration(this.windowStates);
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletConfig.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletConfig.java
new file mode 100644
index 00000000000..249429e1a13
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletConfig.java
@@ -0,0 +1,116 @@
+/*
+ * 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.web.portlet;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.LinkedHashMap;
+import java.util.Collections;
+import javax.portlet.PortletConfig;
+import javax.portlet.PortletContext;
+
+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 resourceBundles = new HashMap();
+
+ private final Map initParameters = new LinkedHashMap();
+
+
+ /**
+ * 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 getInitParameterNames() {
+ return Collections.enumeration(this.initParameters.keySet());
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletContext.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletContext.java
new file mode 100644
index 00000000000..336b21e95b4
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletContext.java
@@ -0,0 +1,254 @@
+/*
+ * 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.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.Map;
+import java.util.Set;
+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 attributes = new LinkedHashMap();
+
+ private final Map initParameters = new LinkedHashMap();
+
+ private String portletContextName = "MockPortletContext";
+
+
+ /**
+ * 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 1;
+ }
+
+ public int getMinorVersion() {
+ return 0;
+ }
+
+ public String getMimeType(String filePath) {
+ return null;
+ }
+
+ 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 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 resourcePaths = new HashSet(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 getAttributeNames() {
+ return Collections.enumeration(this.attributes.keySet());
+ }
+
+ 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 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 portletContextName;
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletPreferences.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletPreferences.java
new file mode 100644
index 00000000000..122ead007e6
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletPreferences.java
@@ -0,0 +1,115 @@
+/*
+ * 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.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 preferences = new LinkedHashMap();
+
+ private final Set readOnly = new HashSet();
+
+
+ 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 getNames() {
+ return Collections.enumeration(this.preferences.keySet());
+ }
+
+ public Map 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);
+ }
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletRequest.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletRequest.java
new file mode 100644
index 00000000000..84c8af733f3
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletRequest.java
@@ -0,0 +1,462 @@
+/*
+ * 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.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 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 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> properties = new LinkedHashMap>();
+
+ private final Map attributes = new LinkedHashMap();
+
+ private final Map parameters = new LinkedHashMap();
+
+ private String authType = null;
+
+ private String contextPath = "";
+
+ private String remoteUser = null;
+
+ private Principal userPrincipal = null;
+
+ private final Set userRoles = new HashSet();
+
+ private boolean secure = false;
+
+ private boolean requestedSessionIdValid = true;
+
+ private final List responseContentTypes = new LinkedList();
+
+ private final List locales = new LinkedList();
+
+ private String scheme = "http";
+
+ private String serverName = "localhost";
+
+ private int serverPort = 80;
+
+
+ /**
+ * 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);
+ }
+
+
+ //---------------------------------------------------------------------
+ // Lifecycle methods
+ //---------------------------------------------------------------------
+
+ /**
+ * 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.
+ * 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 list = new LinkedList();
+ list.add(value);
+ this.properties.put(key, list);
+ }
+
+ /**
+ * Add a single value for the specified property.
+ * 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 oldList = this.properties.get(key);
+ if (oldList != null) {
+ oldList.add(value);
+ }
+ else {
+ List list = new LinkedList();
+ list.add(value);
+ this.properties.put(key, list);
+ }
+ }
+
+ public String getProperty(String key) {
+ Assert.notNull(key, "Property key must not be null");
+ List list = this.properties.get(key);
+ return (list != null && list.size() > 0 ? (String) list.get(0) : null);
+ }
+
+ public Enumeration getProperties(String key) {
+ Assert.notNull(key, "property key must not be null");
+ return Collections.enumeration(this.properties.get(key));
+ }
+
+ public Enumeration 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 getAttributeNames() {
+ checkActive();
+ return Collections.enumeration(this.attributes.keySet());
+ }
+
+ public void setParameters(Map 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 getParameterNames() {
+ return Collections.enumeration(this.parameters.keySet());
+ }
+
+ public String[] getParameterValues(String name) {
+ return this.parameters.get(name);
+ }
+
+ public Map 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 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 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;
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletRequestDispatcher.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletRequestDispatcher.java
new file mode 100644
index 00000000000..2703c1920c2
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletRequestDispatcher.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web.portlet;
+
+import java.io.IOException;
+
+import javax.portlet.PortletException;
+import javax.portlet.PortletRequestDispatcher;
+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 {
+ Assert.notNull(request, "Request must not be null");
+ Assert.notNull(response, "Response must not be null");
+ if (!(response instanceof MockRenderResponse)) {
+ throw new IllegalArgumentException("MockPortletRequestDispatcher requires MockRenderResponse");
+ }
+ ((MockRenderResponse) response).setIncludedUrl(this.url);
+ if (logger.isDebugEnabled()) {
+ logger.debug("MockPortletRequestDispatcher: including URL [" + this.url + "]");
+ }
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletResponse.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletResponse.java
new file mode 100644
index 00000000000..2c94ddda4a5
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletResponse.java
@@ -0,0 +1,110 @@
+/*
+ * 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.web.portlet;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.portlet.PortalContext;
+import javax.portlet.PortletResponse;
+
+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 properties = new LinkedHashMap();
+
+
+ /**
+ * 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 getPropertyNames() {
+ return 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;
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletSession.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletSession.java
new file mode 100644
index 00000000000..ab27defe000
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletSession.java
@@ -0,0 +1,190 @@
+/*
+ * 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.web.portlet;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletSession;
+
+/**
+ * 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 portletAttributes = new HashMap();
+
+ private final Map applicationAttributes = new HashMap();
+
+ 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 getAttributeNames() {
+ return Collections.enumeration(this.portletAttributes.keySet());
+ }
+
+ public Enumeration getAttributeNames(int scope) {
+ if (scope == PortletSession.PORTLET_SCOPE) {
+ return Collections.enumeration(this.portletAttributes.keySet());
+ }
+ else if (scope == PortletSession.APPLICATION_SCOPE) {
+ return Collections.enumeration(this.applicationAttributes.keySet());
+ }
+ 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;
+ }
+
+ public void invalidate() {
+ this.invalid = true;
+ this.portletAttributes.clear();
+ this.applicationAttributes.clear();
+ }
+
+ public boolean isInvalid() {
+ return 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 portletContext;
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletURL.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletURL.java
new file mode 100644
index 00000000000..edf889cf93e
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockPortletURL.java
@@ -0,0 +1,190 @@
+/*
+ * 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.web.portlet;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.portlet.PortalContext;
+import javax.portlet.PortletMode;
+import javax.portlet.PortletModeException;
+import javax.portlet.PortletSecurityException;
+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 implements PortletURL {
+
+ public static final String URL_TYPE_RENDER = "render";
+
+ public static final String URL_TYPE_ACTION = "action";
+
+ private static final String ENCODING = "UTF-8";
+
+
+ private final PortalContext portalContext;
+
+ private final String urlType;
+
+ private WindowState windowState;
+
+ private PortletMode portletMode;
+
+ private final Map parameters = new LinkedHashMap();
+
+ private boolean secure = false;
+
+
+ /**
+ * 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 void setPortletMode(PortletMode portletMode) throws PortletModeException {
+ if (!CollectionUtils.contains(this.portalContext.getSupportedPortletModes(), portletMode)) {
+ throw new PortletModeException("PortletMode not supported", portletMode);
+ }
+ this.portletMode = portletMode;
+ }
+
+ 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 parameters) {
+ Assert.notNull(parameters, "Parameters Map must not be null");
+ this.parameters.clear();
+ for (Iterator it = parameters.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ Assert.isTrue(entry.getKey() instanceof String, "Key must be of type String");
+ Assert.isTrue(entry.getValue() instanceof String[], "Value must be of type String[]");
+ this.parameters.put((String) entry.getKey(), (String[]) entry.getValue());
+ }
+ }
+
+ public Set 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 getParameterMap() {
+ return Collections.unmodifiableMap(this.parameters);
+ }
+
+ public void setSecure(boolean secure) throws PortletSecurityException {
+ this.secure = secure;
+ }
+
+ public boolean isSecure() {
+ return this.secure;
+ }
+
+
+ private String encodeParameter(String name, String value) {
+ try {
+ return URLEncoder.encode(name, ENCODING) + "=" + URLEncoder.encode(value, ENCODING);
+ }
+ catch (UnsupportedEncodingException ex) {
+ return null;
+ }
+ }
+
+ private 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 ? ";" : "") +
+ URLEncoder.encode(name, ENCODING) + "=" +
+ URLEncoder.encode(values[i], ENCODING));
+ }
+ return sb.toString();
+ }
+ catch (UnsupportedEncodingException ex) {
+ return null;
+ }
+ }
+
+
+ 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 entry : this.parameters.entrySet()) {
+ sb.append(";").append(encodeParameter("param_" + entry.getKey(), entry.getValue()));
+ }
+ return (this.secure ? "https:" : "http:") +
+ "//localhost/mockportlet?" + sb.toString();
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockRenderRequest.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockRenderRequest.java
new file mode 100644
index 00000000000..5bb8c54e42d
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockRenderRequest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.mock.web.portlet;
+
+import javax.portlet.PortalContext;
+import javax.portlet.PortletContext;
+import javax.portlet.PortletMode;
+import javax.portlet.RenderRequest;
+
+/**
+ * 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}.
+ * @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);
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockRenderResponse.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockRenderResponse.java
new file mode 100644
index 00000000000..c63e9a44599
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockRenderResponse.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.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.Locale;
+
+import javax.portlet.PortalContext;
+import javax.portlet.PortletURL;
+import javax.portlet.RenderResponse;
+
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Mock implementation of the {@link javax.portlet.RenderResponse} interface.
+ *
+ * @author John A. Lewis
+ * @author Juergen Hoeller
+ * @since 2.0
+ */
+public class MockRenderResponse extends MockPortletResponse implements RenderResponse {
+
+ private String contentType;
+
+ private String namespace = "MockPortlet";
+
+ private String title;
+
+ 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 boolean committed;
+
+ private String includedUrl;
+
+
+ /**
+ * 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);
+ }
+
+
+ //---------------------------------------------------------------------
+ // RenderResponse methods
+ //---------------------------------------------------------------------
+
+ public String getContentType() {
+ return this.contentType;
+ }
+
+ public PortletURL createRenderURL() {
+ PortletURL url = new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_RENDER);
+ return url;
+ }
+
+ public PortletURL createActionURL() {
+ PortletURL url = new MockPortletURL(getPortalContext(), MockPortletURL.URL_TYPE_ACTION);
+ return url;
+ }
+
+ public String getNamespace() {
+ return this.namespace;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = 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;
+ }
+
+
+ //---------------------------------------------------------------------
+ // Methods for MockPortletRequestDispatcher
+ //---------------------------------------------------------------------
+
+ public void setIncludedUrl(String includedUrl) {
+ this.includedUrl = includedUrl;
+ }
+
+ public String getIncludedUrl() {
+ return includedUrl;
+ }
+
+}
diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/package.html b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/package.html
new file mode 100644
index 00000000000..3fa19b640c7
--- /dev/null
+++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/package.html
@@ -0,0 +1,13 @@
+
+
+
+A comprehensive set of Portlet API mock objects,
+targeted at usage with Spring's web MVC framework.
+Useful for testing web contexts and controllers.
+
+More convenient to use than dynamic mock objects
+(EasyMock) or
+existing Portlet API mock objects.
+
+
+
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/DataSourceReport.jasper b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/DataSourceReport.jasper
new file mode 100644
index 00000000000..8414447e09d
Binary files /dev/null and b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/DataSourceReport.jasper differ
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/DataSourceReport.jrxml b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/DataSourceReport.jrxml
new file mode 100644
index 00000000000..2df89cfb9e7
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/DataSourceReport.jrxml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/messages_de.properties b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/messages_de.properties
new file mode 100644
index 00000000000..4dd7d2be865
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/messages_de.properties
@@ -0,0 +1 @@
+page=MeineSeite
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportChild.jasper b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportChild.jasper
new file mode 100644
index 00000000000..cd8ad2052ff
Binary files /dev/null and b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportChild.jasper differ
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportChild.jrxml b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportChild.jrxml
new file mode 100644
index 00000000000..ede2a092214
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportChild.jrxml
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportParent.jasper b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportParent.jasper
new file mode 100644
index 00000000000..aba7bb207d4
Binary files /dev/null and b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportParent.jasper differ
diff --git a/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportParent.jrxml b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportParent.jrxml
new file mode 100644
index 00000000000..f2f9c08d3f9
--- /dev/null
+++ b/org.springframework.web.servlet/src/test/resources/org/springframework/ui/jasperreports/subReportParent.jrxml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+