SPR-8484 Add path variables to the model via AbstractView.render(..) rather than HandlerMethodArgumentResolver
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ public class MappingJacksonJsonView extends AbstractView {
|
||||
*/
|
||||
public MappingJacksonJsonView() {
|
||||
setContentType(DEFAULT_CONTENT_TYPE);
|
||||
setExposePathVariables(false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user