From a0e2b6b13b8dff20ea64dd4d2dfa5bb6a187f32e Mon Sep 17 00:00:00 2001 From: Keith Donald Date: Thu, 30 Jul 2009 11:18:39 +0000 Subject: [PATCH] initial BindingLifecycle @MVC integration git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@1659 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../ui/MvcBindingLifecycle.java | 7 +- .../core/collection/CompositeIterator.java | 77 ++++ .../core/collection/SharedMap.java | 45 +++ .../core/collection/SharedMapDecorator.java | 106 ++++++ .../collection/StringKeyedMapAdapter.java | 348 ++++++++++++++++++ .../core/collection/package-info.java | 6 + .../springframework/util/CollectionUtils.java | 32 ++ .../portlet/context/PortletWebRequest.java | 8 +- .../support/HandlerMethodInvoker.java | 37 +- .../support/PresentationModelUtils.java | 65 ++++ .../ServletAnnotationControllerTests.java | 61 ++- .../web/context/request/FacesWebRequest.java | 6 + .../request/NativeWebRequestParameterMap.java | 86 +++++ .../context/request/ServletWebRequest.java | 7 + .../web/context/request/WebRequest.java | 8 + 15 files changed, 891 insertions(+), 8 deletions(-) create mode 100644 org.springframework.core/src/main/java/org/springframework/core/collection/CompositeIterator.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/collection/SharedMap.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/collection/SharedMapDecorator.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/collection/StringKeyedMapAdapter.java create mode 100644 org.springframework.core/src/main/java/org/springframework/core/collection/package-info.java create mode 100644 org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/PresentationModelUtils.java create mode 100644 org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequestParameterMap.java diff --git a/org.springframework.context/src/main/java/org/springframework/ui/MvcBindingLifecycle.java b/org.springframework.context/src/main/java/org/springframework/ui/MvcBindingLifecycle.java index 7b2a1fafdae..ba21c13d4c6 100644 --- a/org.springframework.context/src/main/java/org/springframework/ui/MvcBindingLifecycle.java +++ b/org.springframework.context/src/main/java/org/springframework/ui/MvcBindingLifecycle.java @@ -39,9 +39,10 @@ public class MvcBindingLifecycle implements BindingLifecycle { private Object model; private PresentationModel presentationModel; - + public MvcBindingLifecycle(Class modelType, PresentationModelFactory presentationModelFactory, ModelMap modelMap, Map fieldValues) { + this.modelType = modelType; this.presentationModelFactory = presentationModelFactory; this.modelMap = modelMap; this.fieldValues = fieldValues; @@ -78,6 +79,10 @@ public class MvcBindingLifecycle implements BindingLifecycle { private void initModel() { try { + if (modelType == null) { + throw new IllegalStateException("Unable to create new model to bind to, no modelType was specified - " + + "did you parameterize the model for your BindingLifecycle declaration?"); + } model = modelType.newInstance(); } catch (InstantiationException e) { throw new IllegalStateException("Model of type [" + modelType.getName() diff --git a/org.springframework.core/src/main/java/org/springframework/core/collection/CompositeIterator.java b/org.springframework.core/src/main/java/org/springframework/core/collection/CompositeIterator.java new file mode 100644 index 00000000000..7241259fe3a --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/collection/CompositeIterator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.collection; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; + +import org.springframework.util.Assert; + +/** + * Iterator that combines multiple other iterators. This is a simple implementation that just maintains a list of + * iterators which are invoked in sequence untill all iterators are exhausted. + * @author Erwin Vervaet + */ +public class CompositeIterator implements Iterator { + + private List> iterators = new LinkedList>(); + + private boolean inUse = false; + + /** + * Create a new composite iterator. Add iterators using the {@link #add(Iterator)} method. + */ + public CompositeIterator() { + } + + /** + * Add given iterator to this composite. + */ + public void add(Iterator iterator) { + Assert.state(!inUse, "You can no longer add iterator to a composite iterator that's already in use"); + if (iterators.contains(iterator)) { + throw new IllegalArgumentException("You cannot add the same iterator twice"); + } + iterators.add(iterator); + } + + public boolean hasNext() { + inUse = true; + for (Iterator> it = iterators.iterator(); it.hasNext();) { + if (it.next().hasNext()) { + return true; + } + } + return false; + } + + public E next() { + inUse = true; + for (Iterator> it = iterators.iterator(); it.hasNext();) { + Iterator iterator = it.next(); + if (iterator.hasNext()) { + return iterator.next(); + } + } + throw new NoSuchElementException("Exhaused all iterators"); + } + + public void remove() { + throw new UnsupportedOperationException("Remove is not supported"); + } +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/collection/SharedMap.java b/org.springframework.core/src/main/java/org/springframework/core/collection/SharedMap.java new file mode 100644 index 00000000000..d6669bbc808 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/collection/SharedMap.java @@ -0,0 +1,45 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.collection; + +import java.util.Map; + +/** + * A simple subinterface of {@link Map} that exposes a mutex that application code can synchronize on. + *

+ * Expected to be implemented by Maps that are backed by shared objects that require synchronization between multiple + * threads. An example would be the HTTP session map. + * + * @author Keith Donald + */ +public interface SharedMap extends Map { + + /** + * Returns the shared mutex that may be synchronized on using a synchronized block. The returned mutex is guaranteed + * to be non-null. + * + * Example usage: + * + *

+	 * synchronized (sharedMap.getMutex()) {
+	 * 	// do synchronized work
+	 * }
+	 * 
+ * + * @return the mutex + */ + public Object getMutex(); +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/collection/SharedMapDecorator.java b/org.springframework.core/src/main/java/org/springframework/core/collection/SharedMapDecorator.java new file mode 100644 index 00000000000..a20c5695981 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/collection/SharedMapDecorator.java @@ -0,0 +1,106 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.collection; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.style.ToStringCreator; + +/** + * A map decorator that implements SharedMap. By default, simply returns the map itself as the mutex. + * Subclasses may override to return a different mutex object. + * + * @author Keith Donald + */ +@SuppressWarnings("serial") +public class SharedMapDecorator implements SharedMap, Serializable { + + /** + * The wrapped, target map. + */ + private Map map; + + /** + * Creates a new shared map decorator. + * @param map the map that is shared by multiple threads, to be synced + */ + public SharedMapDecorator(Map map) { + this.map = map; + } + + // implementing Map + + public void clear() { + map.clear(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public Set> entrySet() { + return map.entrySet(); + } + + public V get(Object key) { + return map.get(key); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public Set keySet() { + return map.keySet(); + } + + public V put(K key, V value) { + return map.put(key, value); + } + + public void putAll(Map map) { + this.map.putAll(map); + } + + public V remove(Object key) { + return map.remove(key); + } + + public int size() { + return map.size(); + } + + public Collection values() { + return map.values(); + } + + // implementing SharedMap + + public Object getMutex() { + return map; + } + + public String toString() { + return new ToStringCreator(this).append("map", map).append("mutex", getMutex()).toString(); + } +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/collection/StringKeyedMapAdapter.java b/org.springframework.core/src/main/java/org/springframework/core/collection/StringKeyedMapAdapter.java new file mode 100644 index 00000000000..9716d59d951 --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/collection/StringKeyedMapAdapter.java @@ -0,0 +1,348 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.core.collection; + +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * Base class for map adapters whose keys are String values. Concrete classes need only implement the abstract hook + * methods defined by this class. + * + * @author Keith Donald + */ +public abstract class StringKeyedMapAdapter implements Map { + + private Set keySet; + + private Collection values; + + private Set> entrySet; + + // implementing Map + + public void clear() { + for (Iterator it = getAttributeNames(); it.hasNext();) { + removeAttribute((String) it.next()); + } + } + + public boolean containsKey(Object key) { + return getAttribute(key.toString()) != null; + } + + public boolean containsValue(Object value) { + if (value == null) { + return false; + } + for (Iterator it = getAttributeNames(); it.hasNext();) { + Object aValue = getAttribute((String) it.next()); + if (value.equals(aValue)) { + return true; + } + } + return false; + } + + public Set> entrySet() { + return (entrySet != null) ? entrySet : (entrySet = new EntrySet()); + } + + public V get(Object key) { + return getAttribute(key.toString()); + } + + public boolean isEmpty() { + return !getAttributeNames().hasNext(); + } + + public Set keySet() { + return (keySet != null) ? keySet : (keySet = new KeySet()); + } + + public V put(String key, V value) { + String stringKey = String.valueOf(key); + V previousValue = getAttribute(stringKey); + setAttribute(stringKey, value); + return previousValue; + } + + public void putAll(Map map) { + for (Map.Entry entry : map.entrySet()) { + setAttribute(entry.getKey().toString(), entry.getValue()); + } + } + + public V remove(Object key) { + String stringKey = key.toString(); + V retval = getAttribute(stringKey); + removeAttribute(stringKey); + return retval; + } + + public int size() { + int size = 0; + for (Iterator it = getAttributeNames(); it.hasNext();) { + size++; + it.next(); + } + return size; + } + + public Collection values() { + return (values != null) ? values : (values = new Values()); + } + + // hook methods + + /** + * Hook method that needs to be implemented by concrete subclasses. Gets a value associated with a key. + * @param key the key to lookup + * @return the associated value, or null if none + */ + protected abstract V getAttribute(String key); + + /** + * Hook method that needs to be implemented by concrete subclasses. Puts a key-value pair in the map, overwriting + * any possible earlier value associated with the same key. + * @param key the key to associate the value with + * @param value the value to associate with the key + */ + protected abstract void setAttribute(String key, V value); + + /** + * Hook method that needs to be implemented by concrete subclasses. Removes a key and its associated value from the + * map. + * @param key the key to remove + */ + protected abstract void removeAttribute(String key); + + /** + * Hook method that needs to be implemented by concrete subclasses. Returns an enumeration listing all keys known to + * the map. + * @return the key enumeration + */ + protected abstract Iterator getAttributeNames(); + + // internal helper classes + + private class KeySet extends AbstractSet { + + public boolean isEmpty() { + return StringKeyedMapAdapter.this.isEmpty(); + } + + public int size() { + return StringKeyedMapAdapter.this.size(); + } + + public void clear() { + StringKeyedMapAdapter.this.clear(); + } + + public Iterator iterator() { + return new KeyIterator(); + } + + public boolean contains(Object o) { + return StringKeyedMapAdapter.this.containsKey(o); + } + + public boolean remove(Object o) { + return StringKeyedMapAdapter.this.remove(o) != null; + } + + } + + private class KeyIterator implements Iterator { + + private final Iterator it = getAttributeNames(); + + private String currentKey; + + public boolean hasNext() { + return it.hasNext(); + } + + public String next() { + return currentKey = it.next(); + } + + public void remove() { + if (currentKey == null) { + throw new NoSuchElementException("You must call next() at least once"); + } + StringKeyedMapAdapter.this.remove(currentKey); + } + + } + + private class Values extends AbstractSet { + + public boolean isEmpty() { + return StringKeyedMapAdapter.this.isEmpty(); + } + + public int size() { + return StringKeyedMapAdapter.this.size(); + } + + public void clear() { + StringKeyedMapAdapter.this.clear(); + } + + public Iterator iterator() { + return new ValuesIterator(); + } + + public boolean contains(Object o) { + return StringKeyedMapAdapter.this.containsValue(o); + } + + public boolean remove(Object o) { + if (o == null) { + return false; + } + for (Iterator it = iterator(); it.hasNext();) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + return false; + } + } + + private class ValuesIterator implements Iterator { + + private final Iterator it = getAttributeNames(); + + private String currentKey; + + public boolean hasNext() { + return it.hasNext(); + } + + public V next() { + currentKey = it.next(); + return StringKeyedMapAdapter.this.get(currentKey); + } + + public void remove() { + if (currentKey == null) { + throw new NoSuchElementException("You must call next() at least once"); + } + StringKeyedMapAdapter.this.remove(currentKey); + } + + } + + private class EntrySet extends AbstractSet> { + + public boolean isEmpty() { + return StringKeyedMapAdapter.this.isEmpty(); + } + + public int size() { + return StringKeyedMapAdapter.this.size(); + } + + public void clear() { + StringKeyedMapAdapter.this.clear(); + } + + public Iterator> iterator() { + return new EntryIterator(); + } + + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry entry = (Entry) o; + Object key = entry.getKey(); + Object value = entry.getValue(); + if (key == null || value == null) { + return false; + } + return value.equals(StringKeyedMapAdapter.this.get(key)); + } + + @SuppressWarnings("unchecked") + public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry entry = (Entry) o; + Object key = entry.getKey(); + Object value = entry.getValue(); + if (key == null || value == null || !value.equals(StringKeyedMapAdapter.this.get(key))) { + return false; + } + return StringKeyedMapAdapter.this.remove(((Entry) o).getKey()) != null; + } + } + + private class EntryIterator implements Iterator> { + + private final Iterator it = getAttributeNames(); + + private String currentKey; + + public boolean hasNext() { + return it.hasNext(); + } + + public Map.Entry next() { + currentKey = it.next(); + return new EntrySetEntry(currentKey); + } + + public void remove() { + if (currentKey == null) { + throw new NoSuchElementException("You must call next() at least once"); + } + StringKeyedMapAdapter.this.remove(currentKey); + } + + } + + private class EntrySetEntry implements Entry { + + private final String currentKey; + + public EntrySetEntry(String currentKey) { + this.currentKey = currentKey; + } + + public String getKey() { + return currentKey; + } + + public V getValue() { + return StringKeyedMapAdapter.this.get(currentKey); + } + + public V setValue(V value) { + return StringKeyedMapAdapter.this.put(currentKey, value); + } + } +} \ No newline at end of file diff --git a/org.springframework.core/src/main/java/org/springframework/core/collection/package-info.java b/org.springframework.core/src/main/java/org/springframework/core/collection/package-info.java new file mode 100644 index 00000000000..4a890ba1f9e --- /dev/null +++ b/org.springframework.core/src/main/java/org/springframework/core/collection/package-info.java @@ -0,0 +1,6 @@ + +/** + * Collection extensions used in the framework. + */ +package org.springframework.core.collection; + diff --git a/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java b/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java index c3ef76869f2..2d92dcabc63 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/org.springframework.core/src/main/java/org/springframework/util/CollectionUtils.java @@ -277,4 +277,36 @@ public abstract class CollectionUtils { return true; } + /** + * Adapts an enumeration to an iterator. + * @param enumeration the enumeration + * @return the iterator + */ + public static Iterator toIterator(Enumeration enumeration) { + return new EnumerationIterator(enumeration); + } + + /** + * Iterator wrapping an Enumeration. + */ + private static class EnumerationIterator implements Iterator { + + private Enumeration enumeration; + + public EnumerationIterator(Enumeration enumeration) { + this.enumeration = enumeration; + } + + public boolean hasNext() { + return enumeration.hasMoreElements(); + } + + public E next() { + return enumeration.nextElement(); + } + + public void remove() throws UnsupportedOperationException { + throw new UnsupportedOperationException("Not supported"); + } + } } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java index fe721beab78..83371246991 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/context/PortletWebRequest.java @@ -17,12 +17,14 @@ package org.springframework.web.portlet.context; import java.security.Principal; +import java.util.Iterator; import java.util.Locale; import java.util.Map; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; import javax.portlet.PortletSession; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.request.NativeWebRequest; @@ -78,7 +80,6 @@ public class PortletWebRequest extends PortletRequestAttributes implements Nativ return getRequest().getProperty(headerName); } - @SuppressWarnings("unchecked") public String[] getHeaderValues(String headerName) { String[] headerValues = StringUtils.toStringArray(getRequest().getProperties(headerName)); return (!ObjectUtils.isEmpty(headerValues) ? headerValues : null); @@ -92,7 +93,10 @@ public class PortletWebRequest extends PortletRequestAttributes implements Nativ return getRequest().getParameterValues(paramName); } - @SuppressWarnings("unchecked") + public Iterator getParameterNames() { + return CollectionUtils.toIterator(getRequest().getParameterNames()); + } + public Map getParameterMap() { return getRequest().getParameterMap(); } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java index 2eb9dc5a473..b6a377620f2 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java @@ -19,6 +19,9 @@ package org.springframework.web.bind.annotation.support; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -29,7 +32,6 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeanUtils; import org.springframework.core.BridgeMethodResolver; import org.springframework.core.Conventions; @@ -40,8 +42,11 @@ import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpInputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.model.ui.PresentationModelFactory; +import org.springframework.model.ui.config.BindingLifecycle; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; +import org.springframework.ui.MvcBindingLifecycle; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; @@ -64,8 +69,10 @@ import org.springframework.web.bind.support.WebArgumentResolver; import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebRequestDataBinder; import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.NativeWebRequestParameterMap; import org.springframework.web.context.request.WebRequest; import org.springframework.web.multipart.MultipartRequest; +import org.springframework.web.servlet.support.PresentationModelUtils; /** * Support class for invoking an annotated handler method. Operates on the introspection results of a {@link @@ -237,6 +244,13 @@ public class HandlerMethodInvoker { throw new IllegalStateException("Errors/BindingResult argument declared " + "without preceding model attribute. Check your handler method signature!"); } + // TODO - Code Review - NEW BINDING LIFECYCLE RESOLVABLE ARG + else if (BindingLifecycle.class.isAssignableFrom(paramType)) { + Class modelType = resolveBindingLifecycleModelType(methodParam); + PresentationModelFactory factory = PresentationModelUtils.getPresentationModelFactory(webRequest); + Map fieldValues = new NativeWebRequestParameterMap(webRequest); + args[i] = new MvcBindingLifecycle(modelType, factory, implicitModel, fieldValues); + } else if (BeanUtils.isSimpleProperty(paramType)) { paramName = ""; } @@ -696,7 +710,7 @@ public class HandlerMethodInvoker { } return WebArgumentResolver.UNRESOLVED; } - + protected final void addReturnValueAsModelAttribute( Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel) { @@ -709,4 +723,23 @@ public class HandlerMethodInvoker { implicitModel.addAttribute(attrName, returnValue); } + // TODO - Code Review - BINDING LIFECYCLE RELATED INTERNAL HELPERS + + // TODO - this generic arg identification looping code is duplicated in several places now... + private Class resolveBindingLifecycleModelType(MethodParameter methodParam) { + Type type = GenericTypeResolver.getTargetType(methodParam); + if (type instanceof ParameterizedType) { + ParameterizedType paramType = (ParameterizedType) type; + Type rawType = paramType.getRawType(); + Type arg = paramType.getActualTypeArguments()[0]; + if (arg instanceof TypeVariable) { + arg = GenericTypeResolver.resolveTypeVariable((TypeVariable) arg, BindingLifecycle.class); + } + if (arg instanceof Class) { + return (Class) arg; + } + } + return null; + } + } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/PresentationModelUtils.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/PresentationModelUtils.java new file mode 100644 index 00000000000..e1e396b3f57 --- /dev/null +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/support/PresentationModelUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.servlet.support; + +import javax.servlet.ServletRequest; + +import org.springframework.model.ui.PresentationModelFactory; +import org.springframework.model.ui.support.DefaultPresentationModelFactory; +import org.springframework.web.context.request.WebRequest; + +/** + * Utilities for working with the model.ui PresentationModel system. + * @author Keith Donald + */ +public final class PresentationModelUtils { + + private static final String PRESENTATION_MODEL_FACTORY_ATTRIBUTE = "presentationModelFactory"; + + private PresentationModelUtils() { + } + + /** + * Get the PresentationModelFactory for the current web request. + * Will create a new one and cache it as a request attribute if one does not exist. + * @param request the web request + * @return the presentation model factory + */ + public static PresentationModelFactory getPresentationModelFactory(WebRequest request) { + PresentationModelFactory factory = (PresentationModelFactory) request.getAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE, WebRequest.SCOPE_REQUEST); + if (factory == null) { + factory = new DefaultPresentationModelFactory(); + request.setAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE, factory, WebRequest.SCOPE_REQUEST); + } + return factory; + } + + /** + * Get the PresentationModelFactory for the current servlet request. + * Will create a new one and cache it as a request attribute if one does not exist. + * @param request the servlet + * @return the presentation model factory + */ + public static PresentationModelFactory getPresentationModelFactory(ServletRequest request) { + PresentationModelFactory factory = (PresentationModelFactory) request.getAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE); + if (factory == null) { + factory = new DefaultPresentationModelFactory(); + request.setAttribute(PRESENTATION_MODEL_FACTORY_ATTRIBUTE, factory); + } + return factory; + } + +} diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index c421f57ff99..9f16c96c58d 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -16,6 +16,14 @@ package org.springframework.web.servlet.mvc.annotation; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.IOException; import java.io.Serializable; import java.io.Writer; @@ -35,6 +43,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; + import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -43,9 +52,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; -import static org.junit.Assert.*; import org.junit.Test; - import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.aop.interceptor.SimpleTraceInterceptor; import org.springframework.aop.support.DefaultPointcutAdvisor; @@ -69,6 +76,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; +import org.springframework.model.ui.config.BindingLifecycle; import org.springframework.stereotype.Controller; import org.springframework.ui.ExtendedModelMap; import org.springframework.ui.Model; @@ -1725,7 +1733,54 @@ public class ServletAnnotationControllerTests { } } - + + @Test + public void testBindingLifecycle() throws Exception { + initServlet(BindingLifecycleController.class); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); + request.addParameter("bar", "test"); + request.addParameter("baz", "12"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("bar=test, baz=12", response.getContentAsString()); + } + + @Controller + public static class BindingLifecycleController { + + @RequestMapping("/test") + public void bind(BindingLifecycle fooLifecycle, Writer writer) throws IOException { + fooLifecycle.execute(); + Foo foo = fooLifecycle.getModel(); + assertEquals("test", foo.getBar()); + assertEquals(new Integer(12), foo.getBaz()); + writer.write("bar=" + foo.getBar() + ", baz=" + foo.getBaz()); + } + + public static final class Foo { + + private String bar; + + private Integer baz; + + public String getBar() { + return bar; + } + + public void setBar(String bar) { + this.bar = bar; + } + + public Integer getBaz() { + return baz; + } + + public void setBaz(Integer baz) { + this.baz = baz; + } + + } + } } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java index 86f51890067..5734b03d6b2 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/FacesWebRequest.java @@ -17,11 +17,13 @@ package org.springframework.web.context.request; import java.security.Principal; +import java.util.Iterator; import java.util.Locale; import java.util.Map; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** @@ -63,6 +65,10 @@ public class FacesWebRequest extends FacesRequestAttributes implements NativeWeb return getExternalContext().getRequestParameterMap().get(paramName); } + public Iterator getParameterNames() { + return getExternalContext().getRequestParameterNames(); + } + public String[] getParameterValues(String paramName) { return getExternalContext().getRequestParameterValuesMap().get(paramName); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequestParameterMap.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequestParameterMap.java new file mode 100644 index 00000000000..a795ca53f53 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/NativeWebRequestParameterMap.java @@ -0,0 +1,86 @@ +/* + * Copyright 2004-2009 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.context.request; + +import java.util.Iterator; + +import org.springframework.core.collection.StringKeyedMapAdapter; +import org.springframework.util.Assert; + +/** + * Map backed by a Web request parameter map for accessing request parameters. + * Also provides support for multi-part requests, providing transparent access to the request "fileMap" as a request parameter entry. + * @author Keith Donald + * @since 3.0 + */ +public class NativeWebRequestParameterMap extends StringKeyedMapAdapter { + + /** + * The wrapped native request. + */ + private NativeWebRequest request; + + /** + * Create a new map wrapping the parameters of given request. + */ + public NativeWebRequestParameterMap(NativeWebRequest request) { + Assert.notNull(request, "The NativeWebRequest is required"); + this.request = request; + } + + protected Object getAttribute(String key) { + /* TODO - MultipartRequest is NOT accessible b/c its in web.servlet + if (request instanceof MultipartRequest) { + MultipartRequest multipartRequest = (MultipartRequest) request; + Object data = multipartRequest.getFileMap().get(key); + if (data != null) { + return data; + } + } + */ + String[] parameters = request.getParameterValues(key); + if (parameters == null) { + return null; + } else if (parameters.length == 1) { + return parameters[0]; + } else { + return parameters; + } + } + + protected void setAttribute(String key, Object value) { + throw new UnsupportedOperationException("WebRequest parameter maps are immutable"); + } + + protected void removeAttribute(String key) { + throw new UnsupportedOperationException("WebRequest parameter maps are immutable"); + } + + protected Iterator getAttributeNames() { + return request.getParameterNames(); + /* TODO - MultipartRequest is NOT accessible b/c its in web.servlet + if (request instanceof MultipartRequest) { + MultipartRequest multipartRequest = (MultipartRequest) request; + CompositeIterator iterator = new CompositeIterator(); + iterator.add(multipartRequest.getFileMap().keySet().iterator()); + iterator.add(CollectionUtils.toIterator(request.getParameterNames())); + return iterator; + } else { + return CollectionUtils.toIterator(request.getParameterNames()); + } + */ + } +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java index 9be308e1959..4cf208481bc 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/ServletWebRequest.java @@ -17,12 +17,14 @@ package org.springframework.web.context.request; import java.security.Principal; +import java.util.Iterator; import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -97,6 +99,11 @@ public class ServletWebRequest extends ServletRequestAttributes implements Nativ return getRequest().getParameterValues(paramName); } + @SuppressWarnings("unchecked") + public Iterator getParameterNames() { + return CollectionUtils.toIterator(getRequest().getParameterNames()); + } + @SuppressWarnings("unchecked") public Map getParameterMap() { return getRequest().getParameterMap(); diff --git a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java index 682435e9013..dd7e4982351 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/context/request/WebRequest.java @@ -17,6 +17,7 @@ package org.springframework.web.context.request; import java.security.Principal; +import java.util.Iterator; import java.util.Locale; import java.util.Map; @@ -61,6 +62,13 @@ public interface WebRequest extends RequestAttributes { */ String[] getParameterValues(String paramName); + /** + * Return a Iterator over request parameter names. + * @see javax.servlet.http.HttpServletRequest#getParameterNames() + * @since 3.0 + */ + Iterator getParameterNames(); + /** * Return a immutable Map of the request parameters, with parameter names as map keys * and parameter values as map values. The map values will be of type String array.