diff --git a/org.springframework.context/src/test/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocatorTests.java b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocatorTests.java new file mode 100644 index 00000000000..9bdca2165cd --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/SingletonBeanFactoryLocatorTests.java @@ -0,0 +1,182 @@ +/* + * 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.beans.factory.access; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.util.ClassUtils; + +/** + * @author Colin Sampaleanu + * @author Chris Beams + */ +public class SingletonBeanFactoryLocatorTests { + + @Test + public void testBasicFunctionality() { + SingletonBeanFactoryLocator facLoc = new SingletonBeanFactoryLocator( + "classpath*:" + ClassUtils.addResourcePathToPackagePath(getClass(), "ref1.xml")); + + basicFunctionalityTest(facLoc); + } + + /** + * Worker method so subclass can use it too. + */ + protected void basicFunctionalityTest(SingletonBeanFactoryLocator facLoc) { + BeanFactoryReference bfr = facLoc.useBeanFactory("a.qualified.name.of.some.sort"); + BeanFactory fac = bfr.getFactory(); + BeanFactoryReference bfr2 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr2.getFactory(); + // verify that the same instance is returned + TestBean tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("beans1.bean1")); + tb.setName("was beans1.bean1"); + BeanFactoryReference bfr3 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr3.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + BeanFactoryReference bfr4 = facLoc.useBeanFactory("a.qualified.name.which.is.an.alias"); + fac = bfr4.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + // Now verify that we can call release in any order. + // Unfortunately this doesn't validate complete release after the last one. + bfr2.release(); + bfr3.release(); + bfr.release(); + bfr4.release(); + } + + /** + * This test can run multiple times, but due to static keyed lookup of the locators, + * 2nd and subsequent calls will actuall get back same locator instance. This is not + * an issue really, since the contained beanfactories will still be loaded and released. + */ + @Test + public void testGetInstance() { + // Try with and without 'classpath*:' prefix, and with 'classpath:' prefix. + BeanFactoryLocator facLoc = SingletonBeanFactoryLocator.getInstance( + ClassUtils.addResourcePathToPackagePath(getClass(), "ref1.xml")); + getInstanceTest1(facLoc); + + facLoc = SingletonBeanFactoryLocator.getInstance( + "classpath*:/" + ClassUtils.addResourcePathToPackagePath(getClass(), "ref1.xml")); + getInstanceTest2(facLoc); + + // This will actually get another locator instance, as the key is the resource name. + facLoc = SingletonBeanFactoryLocator.getInstance( + "classpath:" + ClassUtils.addResourcePathToPackagePath(getClass(), "ref1.xml")); + getInstanceTest3(facLoc); + + } + + /** + * Worker method so subclass can use it too + */ + protected void getInstanceTest1(BeanFactoryLocator facLoc) { + BeanFactoryReference bfr = facLoc.useBeanFactory("a.qualified.name.of.some.sort"); + BeanFactory fac = bfr.getFactory(); + BeanFactoryReference bfr2 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr2.getFactory(); + // verify that the same instance is returned + TestBean tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("beans1.bean1")); + tb.setName("was beans1.bean1"); + BeanFactoryReference bfr3 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr3.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + + BeanFactoryReference bfr4 = facLoc.useBeanFactory("a.qualified.name.which.is.an.alias"); + fac = bfr4.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + + bfr.release(); + bfr3.release(); + bfr2.release(); + bfr4.release(); + } + + /** + * Worker method so subclass can use it too + */ + protected void getInstanceTest2(BeanFactoryLocator facLoc) { + BeanFactoryReference bfr; + BeanFactory fac; + BeanFactoryReference bfr2; + TestBean tb; + BeanFactoryReference bfr3; + BeanFactoryReference bfr4; + bfr = facLoc.useBeanFactory("a.qualified.name.of.some.sort"); + fac = bfr.getFactory(); + bfr2 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr2.getFactory(); + // verify that the same instance is returned + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("beans1.bean1")); + tb.setName("was beans1.bean1"); + bfr3 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr3.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + bfr4 = facLoc.useBeanFactory("a.qualified.name.which.is.an.alias"); + fac = bfr4.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + bfr.release(); + bfr2.release(); + bfr4.release(); + bfr3.release(); + } + + /** + * Worker method so subclass can use it too + */ + protected void getInstanceTest3(BeanFactoryLocator facLoc) { + BeanFactoryReference bfr; + BeanFactory fac; + BeanFactoryReference bfr2; + TestBean tb; + BeanFactoryReference bfr3; + BeanFactoryReference bfr4; + bfr = facLoc.useBeanFactory("a.qualified.name.of.some.sort"); + fac = bfr.getFactory(); + bfr2 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr2.getFactory(); + // verify that the same instance is returned + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("beans1.bean1")); + tb.setName("was beans1.bean1"); + bfr3 = facLoc.useBeanFactory("another.qualified.name"); + fac = bfr3.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + bfr4 = facLoc.useBeanFactory("a.qualified.name.which.is.an.alias"); + fac = bfr4.getFactory(); + tb = (TestBean) fac.getBean("beans1.bean1"); + assertTrue(tb.getName().equals("was beans1.bean1")); + bfr4.release(); + bfr3.release(); + bfr2.release(); + bfr.release(); + } + +} diff --git a/org.springframework.context/src/test/java/org/springframework/beans/factory/access/TestBean.java b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/TestBean.java new file mode 100644 index 00000000000..eaf1d5ec0b0 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/TestBean.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2005 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.access; + +import java.util.List; + +/** + * Scrap bean for use in tests. + * + * @author Colin Sampaleanu + */ +public class TestBean { + + private String name; + + private List list; + + private Object objRef; + + /** + * @return Returns the name. + */ + public String getName() { + return name; + } + + /** + * @param name The name to set. + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return Returns the list. + */ + public List getList() { + return list; + } + + /** + * @param list The list to set. + */ + public void setList(List list) { + this.list = list; + } + + /** + * @return Returns the object. + */ + public Object getObjRef() { + return objRef; + } + + /** + * @param object The object to set. + */ + public void setObjRef(Object object) { + this.objRef = object; + } +} diff --git a/org.springframework.context/src/test/java/org/springframework/beans/factory/access/beans1.xml b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/beans1.xml new file mode 100644 index 00000000000..10581610497 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/beans1.xml @@ -0,0 +1,16 @@ + + + + + + + + beans1.bean1 + + + + bean2 + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/beans/factory/access/beans2.xml b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/beans2.xml new file mode 100644 index 00000000000..a5167f6bfe6 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/beans2.xml @@ -0,0 +1,16 @@ + + + + + + + + beans2.bean1 + + + + beans2.bean2 + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/beans/factory/access/ref1.xml b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/ref1.xml new file mode 100644 index 00000000000..2dde7a31b37 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/beans/factory/access/ref1.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java b/org.springframework.context/src/test/java/org/springframework/mock/jndi/ExpectedLookupTemplate.java new file mode 100644 index 00000000000..28670fd4383 --- /dev/null +++ b/org.springframework.context/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.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: + *

+ * + *

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: + *

+ * + *

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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <band height="70"> + <line> + <reportElement x="0" y="0" width="515" height="1"/> + <graphicElement/> + </line> + <textField isBlankWhenNull="true"> + <reportElement x="0" y="10" width="515" height="30"/> + <textElement textAlignment="Center"> + <font reportFont="Arial_Normal" size="22"/> + </textElement> + <textFieldExpression class="java.lang.String"><![CDATA[$P{ReportTitle}]]></textFieldExpression> + </textField> + <textField isBlankWhenNull="true"> + <reportElement x="0" y="40" width="515" height="20"/> + <textElement textAlignment="Center"> + <font reportFont="Arial_Normal" size="14"/> + </textElement> + <textFieldExpression class="java.lang.String"><![CDATA[$P{DataFile}]]></textFieldExpression> + </textField> + </band> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <band height="14"> + <staticText> + <reportElement x="0" y="2" width="60" height="10"/> + <textElement> + <font reportFont="Arial_Italic"/> + </textElement> + <text><![CDATA[Title]]></text> + </staticText> + <textField> + <reportElement x="0" y="2" width="325" height="10"/> + <textElement textAlignment="Center"> + <font reportFont="Arial_Bold"/> + </textElement> + <textFieldExpression class="java.lang.String"><![CDATA["Products ordered by people in " + $P{City}]]></textFieldExpression> + </textField> + </band> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + <band height="50"> + <line> + <reportElement x="0" y="0" width="515" height="1"/> + <graphicElement/> + </line> + <staticText> + <reportElement x="0" y="10" width="515" height="30"/> + <textElement textAlignment="Center"> + <font reportFont="Arial_Normal" size="22"/> + </textElement> + <text><![CDATA[Master Report]]></text> + </staticText> + </band> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +