SPR-8484 Add path variables to the model via AbstractView.render(..) rather than HandlerMethodArgumentResolver

This commit is contained in:
Rossen Stoyanchev
2011-06-24 10:09:28 +00:00
parent df5e4d6d56
commit 2122cbcb1b
17 changed files with 422 additions and 162 deletions

View File

@@ -45,11 +45,18 @@ public interface View {
/**
* Name of the {@link HttpServletRequest} attribute that contains the response status code.
* <p>Note: This attribute is not required to be supported by all
* View implementations.
* <p>Note: This attribute is not required to be supported by all View implementations.
*/
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
/**
* Name of the {@link HttpServletRequest} attribute that contains a Map with path variables.
* The map consists of String-based URI template variable names as keys and their corresponding
* Object-based values -- extracted from segments of the URL and type converted.
*
* <p>Note: This attribute is not required to be supported by all View implementations.
*/
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
/**
* Return the content type of the view, if predetermined.

View File

@@ -16,10 +16,9 @@
package org.springframework.web.servlet.mvc.method.annotation.support;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.WebDataBinder;
@@ -30,6 +29,7 @@ import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.method.annotation.support.AbstractNamedValueMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.View;
/**
* Resolves method arguments annotated with an @{@link PathVariable}.
@@ -78,14 +78,20 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
}
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(Object arg,
String name,
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) {
if (mavContainer != null) {
mavContainer.addAttribute(name, arg);
NativeWebRequest request) {
String key = View.PATH_VARIABLES;
int scope = RequestAttributes.SCOPE_REQUEST;
Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
if (pathVars == null) {
pathVars = new HashMap<String, Object>();
request.setAttribute(key, pathVars, scope);
}
pathVars.put(name, arg);
}
private static class PathVariableNamedValueInfo extends NamedValueInfo {

View File

@@ -70,6 +70,8 @@ public abstract class AbstractView extends WebApplicationObjectSupport implement
/** Map of static attributes, keyed by attribute name (String) */
private final Map<String, Object> staticAttributes = new HashMap<String, Object>();
/** Whether or not the view should add path variables in the model */
private boolean exposePathVariables = true;
/**
* Set the view's name. Helpful for traceability.
@@ -216,10 +218,31 @@ public abstract class AbstractView extends WebApplicationObjectSupport implement
* manipulating the Map but rather just for checking the contents.
* @return the static attributes in this view
*/
public Map getStaticAttributes() {
public Map<String, Object> getStaticAttributes() {
return Collections.unmodifiableMap(this.staticAttributes);
}
/**
* Whether to add path variables in the model or not.
* <p>Path variables are commonly bound to URI template variables through the {@code @PathVariable}
* annotation. They're are effectively URI template variables with type conversion applied to
* them to derive typed Object values. Such values are frequently needed in views for
* constructing links to the same and other URLs.
* <p>Path variables added to the model override static attributes (see {@link #setAttributes(Properties)})
* but not attributes already present in the model.
* <p>By default this flag is set to {@code true}. Concrete view types can override this.
* @param exposePathVariables {@code true} to expose path variables, and {@code false} otherwise.
*/
public void setExposePathVariables(boolean exposePathVariables) {
this.exposePathVariables = exposePathVariables;
}
/**
* Returns the value of the flag indicating whether path variables should be added to the model or not.
*/
public boolean isExposePathVariables() {
return exposePathVariables;
}
/**
* Prepares the view given the specified model, merging it with static
@@ -232,11 +255,20 @@ public abstract class AbstractView extends WebApplicationObjectSupport implement
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null;
// Consolidate static and dynamic model attributes.
Map<String, Object> mergedModel =
new HashMap<String, Object>(this.staticAttributes.size() + (model != null ? model.size() : 0));
int size = this.staticAttributes.size();
size += (model != null) ? model.size() : 0;
size += (pathVars != null) ? pathVars.size() : 0;
Map<String, Object> mergedModel = new HashMap<String, Object>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
}

View File

@@ -238,9 +238,9 @@ public class RedirectView extends AbstractUrlBasedView {
model = removeKeys(model, redirectUri.getVariableNames());
}
if (this.exposeModelAttributes) {
List<String> uriTemplateVarNames = getUriTemplateVarNames(request);
if (!uriTemplateVarNames.isEmpty()) {
model = removeKeys(model, uriTemplateVarNames);
List<String> pathVarNames = getPathVarNames(request);
if (!pathVarNames.isEmpty()) {
model = removeKeys(model, pathVarNames);
}
appendQueryProperties(targetUrl, model, enc);
}
@@ -274,12 +274,12 @@ public class RedirectView extends AbstractUrlBasedView {
}
/**
* Returns URI template variable names for the current request; or an empty list.
* Returns the names of PathVariable for the current request; or an empty list.
*/
@SuppressWarnings("unchecked")
private List<String> getUriTemplateVarNames(HttpServletRequest request) {
String key = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Map<String, String> map = (Map<String, String>) request.getAttribute(key);
private List<String> getPathVarNames(HttpServletRequest request) {
String key = View.PATH_VARIABLES;
Map<String, Object> map = (Map<String, Object>) request.getAttribute(key);
return (map != null) ? new ArrayList<String>(map.keySet()) : Collections.<String>emptyList();
}
@@ -307,7 +307,7 @@ public class RedirectView extends AbstractUrlBasedView {
boolean first = (getUrl().indexOf('?') < 0);
for (Map.Entry<String, Object> entry : queryProperties(model).entrySet()) {
Object rawValue = entry.getValue();
Iterator valueIter;
Iterator<Object> valueIter;
if (rawValue != null && rawValue.getClass().isArray()) {
valueIter = Arrays.asList(ObjectUtils.toObjectArray(rawValue)).iterator();
}

View File

@@ -121,6 +121,7 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
/** Map of static attributes, keyed by attribute name (String) */
private final Map<String, Object> staticAttributes = new HashMap<String, Object>();
private Boolean exposePathVariables;
/**
* Set the view class that should be used to create views.
@@ -337,6 +338,22 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
return this.order;
}
/**
* Whether views resolved by this resolver should add path variables the model or not.
* The default setting is to allow each View decide (see {@link AbstractView#setExposePathVariables(boolean)}.
* However, you can use this property to override that.
* @param exposePathVariables
* <ul>
* <li>{@code true} - all Views resolved by this resolver will expose path variables
* <li>{@code false} - no Views resolved by this resolver will expose path variables
* <li>{@code null} - individual Views can decide for themselves (this is used by the default)
* <ul>
* @see AbstractView#setExposePathVariables(boolean)
*/
public void setExposePathVariables(Boolean exposePathVariables) {
this.exposePathVariables = exposePathVariables;
}
@Override
protected void initApplicationContext() {
super.initApplicationContext();
@@ -345,7 +362,6 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
}
}
/**
* This implementation returns just the view name,
* as this ViewResolver doesn't support localized resolution.
@@ -444,6 +460,9 @@ public class UrlBasedViewResolver extends AbstractCachingViewResolver implements
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
if (this.exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}

View File

@@ -73,6 +73,7 @@ public class MappingJacksonJsonView extends AbstractView {
*/
public MappingJacksonJsonView() {
setContentType(DEFAULT_CONTENT_TYPE);
setExposePathVariables(false);
}
/**

View File

@@ -18,6 +18,7 @@ package org.springframework.web.servlet.view.xml;
import java.io.ByteArrayOutputStream;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -58,6 +59,7 @@ public class MarshallingView extends AbstractView {
*/
public MarshallingView() {
setContentType(DEFAULT_CONTENT_TYPE);
setExposePathVariables(false);
}
/**
@@ -67,6 +69,7 @@ public class MarshallingView extends AbstractView {
Assert.notNull(marshaller, "'marshaller' must not be null");
setContentType(DEFAULT_CONTENT_TYPE);
this.marshaller = marshaller;
setExposePathVariables(false);
}
/**
@@ -93,8 +96,9 @@ public class MarshallingView extends AbstractView {
}
@Override
protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
Object toBeMarshalled = locateToBeMarshalled(model);
if (toBeMarshalled == null) {
throw new ServletException("Unable to locate object to be marshalled in model: " + model);
@@ -119,7 +123,7 @@ public class MarshallingView extends AbstractView {
* supported by the marshaller
* @see #setModelKey(String)
*/
protected Object locateToBeMarshalled(Map model) throws ServletException {
protected Object locateToBeMarshalled(Map<String, Object> model) throws ServletException {
if (this.modelKey != null) {
Object o = model.get(this.modelKey);
if (o == null) {