#169 - Added support for creating URI templates by pointing to controller methods.
If not all parameters are handed to a fake controller method invocation, the resulting link is now becoming a URI template. That means there are some tiny changes to the general behavior:
1. Missing required parts (like a path segment) are not rejected immediately but only if the resulting link is expanded itself.
2. Missing request parameters now appear in the resulting link, either in mandatory form (e.g. ?foo={foo}) or optionally (e.g. /{?foo}). To resolve these, the Link has to be expanded in turn.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2016 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.hateoas.core;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
/**
|
||||
* Utilities for URI encoding.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 0.22
|
||||
* @soundtrack Don Philippe - Between Now And Now (Between Now And Now)
|
||||
*/
|
||||
@UtilityClass
|
||||
public class EncodingUtils {
|
||||
|
||||
private static final String ENCODING = "UTF-8";
|
||||
|
||||
/**
|
||||
* Encodes the given path value.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodePath(Object source) {
|
||||
|
||||
Assert.notNull(source, "Path value must not be null!");
|
||||
|
||||
try {
|
||||
return UriUtils.encodePath(source.toString(), ENCODING);
|
||||
} catch (UnsupportedEncodingException o_O) {
|
||||
throw new IllegalStateException(o_O);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given request parameter value.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodeParameter(Object source) {
|
||||
|
||||
Assert.notNull(source, "Request parameter value must not be null!");
|
||||
|
||||
try {
|
||||
return UriUtils.encodeQueryParam(source.toString(), ENCODING);
|
||||
} catch (UnsupportedEncodingException o_O) {
|
||||
throw new IllegalStateException(o_O);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given fragment value.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static String encodeFragment(Object source) {
|
||||
|
||||
Assert.notNull(source, "Fragment value must not be null!");
|
||||
|
||||
try {
|
||||
return UriUtils.encodeFragment(source.toString(), ENCODING);
|
||||
} catch (UnsupportedEncodingException o_O) {
|
||||
throw new IllegalStateException(o_O);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.hateoas.core;
|
||||
|
||||
import static org.springframework.hateoas.core.EncodingUtils.*;
|
||||
import static org.springframework.web.util.UriComponentsBuilder.*;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -85,18 +86,22 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
return getThis();
|
||||
}
|
||||
|
||||
return slash(UriComponentsBuilder.fromUriString(path).build(), false);
|
||||
}
|
||||
|
||||
protected T slash(UriComponents components, boolean encoded) {
|
||||
|
||||
String uriString = uriComponents.toUriString();
|
||||
UriComponentsBuilder builder = uriString.isEmpty() ? fromUri(uriComponents.toUri()) : fromUriString(uriString);
|
||||
|
||||
UriComponents components = UriComponentsBuilder.fromUriString(path).build();
|
||||
|
||||
for (String pathSegment : components.getPathSegments()) {
|
||||
builder.pathSegment(pathSegment);
|
||||
builder.pathSegment(encoded ? pathSegment : encodePath(pathSegment));
|
||||
}
|
||||
|
||||
String fragment = components.getFragment();
|
||||
|
||||
if (StringUtils.hasText(fragment)) {
|
||||
builder.fragment(fragment);
|
||||
builder.fragment(encoded ? fragment : encodeFragment(fragment));
|
||||
}
|
||||
|
||||
return createNewInstance(builder.query(components.getQuery()));
|
||||
@@ -120,7 +125,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
* @see org.springframework.hateoas.LinkBuilder#toUri()
|
||||
*/
|
||||
public URI toUri() {
|
||||
return uriComponents.encode().toUri();
|
||||
return uriComponents.encode().toUri().normalize();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -128,7 +133,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
* @see org.springframework.hateoas.LinkBuilder#withRel(java.lang.String)
|
||||
*/
|
||||
public Link withRel(String rel) {
|
||||
return new Link(this.toString(), rel);
|
||||
return new Link(toString(), rel);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -145,7 +150,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return toUri().normalize().toASCIIString();
|
||||
return uriComponents.toUriString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.hateoas.core.LinkBuilderSupport;
|
||||
import org.springframework.hateoas.core.MappingDiscoverer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.DefaultUriTemplateHandler;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -38,6 +39,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
public class JaxRsLinkBuilder extends LinkBuilderSupport<JaxRsLinkBuilder> {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(Path.class);
|
||||
private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
|
||||
|
||||
/**
|
||||
* Creates a new {@link JaxRsLinkBuilder} from the given {@link UriComponentsBuilder}.
|
||||
@@ -72,10 +74,11 @@ public class JaxRsLinkBuilder extends LinkBuilderSupport<JaxRsLinkBuilder> {
|
||||
Assert.notNull(resourceType, "Controller type must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(DISCOVERER.getMapping(resourceType)).build();
|
||||
UriComponents expandedComponents = uriComponents.expand(parameters);
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(DISCOVERER.getMapping(resourceType));
|
||||
UriComponents expandedComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new JaxRsLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping()).slash(expandedComponents);
|
||||
return new JaxRsLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping())//
|
||||
.slash(expandedComponents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,10 +95,11 @@ public class JaxRsLinkBuilder extends LinkBuilderSupport<JaxRsLinkBuilder> {
|
||||
Assert.notNull(resourceType, "Controller type must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(DISCOVERER.getMapping(resourceType)).build();
|
||||
UriComponents expandedComponents = uriComponents.expand(parameters);
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(DISCOVERER.getMapping(resourceType));
|
||||
UriComponents expandedComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new JaxRsLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping()).slash(expandedComponents);
|
||||
return new JaxRsLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping())//
|
||||
.slash(expandedComponents, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -115,4 +119,29 @@ public class JaxRsLinkBuilder extends LinkBuilderSupport<JaxRsLinkBuilder> {
|
||||
protected JaxRsLinkBuilder createNewInstance(UriComponentsBuilder builder) {
|
||||
return new JaxRsLinkBuilder(builder);
|
||||
}
|
||||
|
||||
private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
|
||||
|
||||
public CustomUriTemplateHandler() {
|
||||
setStrictEncoding(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package org.springframework.hateoas.mvc;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
@@ -39,24 +42,13 @@ import org.springframework.web.util.UriTemplate;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class AnnotatedParametersParameterAccessor {
|
||||
|
||||
private static final Map<Method, MethodParameters> METHOD_PARAMETERS_CACHE = new ConcurrentReferenceHashMap<Method, MethodParameters>(
|
||||
16, ReferenceType.WEAK);
|
||||
|
||||
private final AnnotationAttribute attribute;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotatedParametersParameterAccessor} using the given {@link AnnotationAttribute}.
|
||||
*
|
||||
* @param attribute must not be {@literal null}.
|
||||
*/
|
||||
public AnnotatedParametersParameterAccessor(AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(attribute);
|
||||
|
||||
this.attribute = attribute;
|
||||
}
|
||||
private final @NonNull AnnotationAttribute attribute;
|
||||
|
||||
/**
|
||||
* Returns {@link BoundMethodParameter}s contained in the given {@link MethodInvocation}.
|
||||
@@ -78,13 +70,27 @@ class AnnotatedParametersParameterAccessor {
|
||||
Object verifiedValue = verifyParameterValue(parameter, value);
|
||||
|
||||
if (verifiedValue != null) {
|
||||
result.add(new BoundMethodParameter(parameter, value, attribute));
|
||||
result.add(createParameter(parameter, verifiedValue, attribute));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link BoundMethodParameter} for the given {@link MethodParameter}, parameter value and
|
||||
* {@link AnnotationAttribute}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
* @param attribute must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected BoundMethodParameter createParameter(MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
return new BoundMethodParameter(parameter, value, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to verifiy the parameter values given for a dummy invocation. Default implementation rejects
|
||||
* {@literal null} values as they indicate an invalid dummy call.
|
||||
@@ -94,17 +100,6 @@ class AnnotatedParametersParameterAccessor {
|
||||
* @return the verified value.
|
||||
*/
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
|
||||
if (value == null) {
|
||||
|
||||
Object indexOrName = StringUtils.hasText(parameter.getParameterName()) ? parameter.getParameterName()
|
||||
: parameter.getParameterIndex();
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Required controller parameter %s of method %s found but null value given!", indexOrName,
|
||||
parameter.getMethod()));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -174,6 +169,7 @@ class AnnotatedParametersParameterAccessor {
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String annotationAttributeValue = attribute.getValueFrom(annotation);
|
||||
|
||||
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName();
|
||||
}
|
||||
|
||||
@@ -192,12 +188,17 @@ class AnnotatedParametersParameterAccessor {
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
return value == null ? null
|
||||
: (String) CONVERSION_SERVICE.convert(value, parameterTypeDecsriptor, STRING_DESCRIPTOR);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (String) CONVERSION_SERVICE.convert(value, parameterTypeDecsriptor, STRING_DESCRIPTOR);
|
||||
/**
|
||||
* Returns whether the given parameter is a required one. Defaults to {@literal true}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,9 @@ package org.springframework.hateoas.mvc;
|
||||
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Delegate;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
@@ -24,16 +27,19 @@ import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.core.LinkBuilderSupport;
|
||||
import org.springframework.hateoas.core.MappingDiscoverer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.DefaultUriTemplateHandler;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
@@ -52,6 +58,9 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
private static final String REQUEST_ATTRIBUTES_MISSING = "Could not find current request via RequestContextHolder. Is this being called from a Spring MVC handler?";
|
||||
private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class);
|
||||
private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory();
|
||||
private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
|
||||
|
||||
private final TemplateVariables variables;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}.
|
||||
@@ -59,7 +68,10 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
ControllerLinkBuilder(UriComponentsBuilder builder) {
|
||||
|
||||
super(builder);
|
||||
|
||||
this.variables = TemplateVariables.NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +80,14 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
* @param uriComponents must not be {@literal null}.
|
||||
*/
|
||||
ControllerLinkBuilder(UriComponents uriComponents) {
|
||||
this(uriComponents, TemplateVariables.NONE);
|
||||
}
|
||||
|
||||
ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables) {
|
||||
|
||||
super(uriComponents);
|
||||
|
||||
this.variables = variables;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,10 +116,10 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping).build();
|
||||
UriComponents expandedComponents = uriComponents.expand(parameters);
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(expandedComponents);
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,10 +138,10 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping).build();
|
||||
UriComponents expandedComponents = uriComponents.expand(parameters);
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(expandedComponents);
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -140,7 +159,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
Assert.notNull(controller, "Controller type must not be null!");
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
UriTemplate template = new UriTemplate(DISCOVERER.getMapping(controller, method));
|
||||
UriTemplate template = DISCOVERER.getMappingAsUriTemplate(controller, method);
|
||||
URI uri = template.expand(parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uri);
|
||||
@@ -211,6 +230,27 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
return UriComponentsBuilder.fromUri(toUri());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
String result = super.toString();
|
||||
|
||||
if (variables == TemplateVariables.NONE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!result.contains("#")) {
|
||||
return result.concat(variables.toString());
|
||||
}
|
||||
|
||||
String[] parts = result.split("#");
|
||||
return parts[0].concat(variables.toString()).concat("#").concat(parts[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the current servlet mapping with the host tweaked in case the
|
||||
* request contains an {@code X-Forwarded-Host} header and the scheme tweaked in case the request contains an
|
||||
@@ -279,4 +319,29 @@ public class ControllerLinkBuilder extends LinkBuilderSupport<ControllerLinkBuil
|
||||
Assert.state(servletRequest != null, "Could not find current HttpServletRequest");
|
||||
return servletRequest;
|
||||
}
|
||||
|
||||
private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
|
||||
|
||||
public CustomUriTemplateHandler() {
|
||||
setStrictEncoding(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@
|
||||
*/
|
||||
package org.springframework.hateoas.mvc;
|
||||
|
||||
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
|
||||
import static org.springframework.hateoas.TemplateVariables.*;
|
||||
import static org.springframework.hateoas.core.EncodingUtils.*;
|
||||
import static org.springframework.web.util.UriComponents.UriTemplateVariables.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -28,6 +33,8 @@ import java.util.Map;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.MethodLinkBuilderFactory;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.core.AnnotationAttribute;
|
||||
import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware;
|
||||
@@ -133,22 +140,50 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
|
||||
UriTemplate template = new UriTemplate(mapping);
|
||||
Map<String, Object> values = new HashMap<String, Object>();
|
||||
|
||||
Iterator<String> names = template.getVariableNames().iterator();
|
||||
|
||||
while (classMappingParameters.hasNext()) {
|
||||
values.put(names.next(), classMappingParameters.next());
|
||||
values.put(names.next(), encodePath(classMappingParameters.next()));
|
||||
}
|
||||
|
||||
for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) {
|
||||
values.put(parameter.getVariableName(), parameter.asString());
|
||||
values.put(parameter.getVariableName(), encodePath(parameter.asString()));
|
||||
}
|
||||
|
||||
List<String> optionalEmptyParameters = new ArrayList<String>();
|
||||
|
||||
for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) {
|
||||
|
||||
bindRequestParameters(builder, parameter);
|
||||
|
||||
if (SKIP_VALUE.equals(parameter.getValue())) {
|
||||
|
||||
values.put(parameter.getVariableName(), SKIP_VALUE);
|
||||
|
||||
if (!parameter.isRequired()) {
|
||||
optionalEmptyParameters.add(parameter.getVariableName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String variable : template.getVariableNames()) {
|
||||
if (!values.containsKey(variable)) {
|
||||
values.put(variable, SKIP_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
UriComponents components = applyUriComponentsContributer(builder, invocation).buildAndExpand(values);
|
||||
return new ControllerLinkBuilder(components);
|
||||
TemplateVariables variables = NONE;
|
||||
|
||||
for (String parameter : optionalEmptyParameters) {
|
||||
|
||||
boolean previousRequestParameter = components.getQueryParams().isEmpty() && variables.equals(NONE);
|
||||
TemplateVariable variable = new TemplateVariable(parameter,
|
||||
previousRequestParameter ? REQUEST_PARAM : REQUEST_PARAM_CONTINUED);
|
||||
variables = variables.concat(variable);
|
||||
}
|
||||
|
||||
return new ControllerLinkBuilder(components, variables);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -204,7 +239,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
|
||||
for (Map.Entry<String, List<String>> multiValueEntry : requestParams.entrySet()) {
|
||||
for (String singleEntryValue : multiValueEntry.getValue()) {
|
||||
builder.queryParam(multiValueEntry.getKey(), singleEntryValue);
|
||||
builder.queryParam(multiValueEntry.getKey(), encodeParameter(singleEntryValue));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,17 +248,23 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
Map<String, String> requestParams = (Map<String, String>) value;
|
||||
|
||||
for (Map.Entry<String, String> requestParamEntry : requestParams.entrySet()) {
|
||||
builder.queryParam(requestParamEntry.getKey(), requestParamEntry.getValue());
|
||||
builder.queryParam(requestParamEntry.getKey(), encodeParameter(requestParamEntry.getValue()));
|
||||
}
|
||||
|
||||
} else if (value instanceof Collection) {
|
||||
|
||||
for (Object element : (Collection<?>) value) {
|
||||
builder.queryParam(key, element);
|
||||
builder.queryParam(key, encodeParameter(element));
|
||||
}
|
||||
|
||||
} else if (SKIP_VALUE.equals(value)) {
|
||||
|
||||
if (parameter.isRequired()) {
|
||||
builder.queryParam(key, String.format("{%s}", parameter.getVariableName()));
|
||||
}
|
||||
|
||||
} else {
|
||||
builder.queryParam(key, parameter.asString());
|
||||
builder.queryParam(key, encodeParameter(parameter.asString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +280,35 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
super(new AnnotationAttribute(RequestParam.class));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#createParameter(org.springframework.core.MethodParameter, java.lang.Object, org.springframework.hateoas.core.AnnotationAttribute)
|
||||
*/
|
||||
@Override
|
||||
protected BoundMethodParameter createParameter(final MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
|
||||
return new BoundMethodParameter(parameter, value, attribute) {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor.BoundMethodParameter#isRequired()
|
||||
*/
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
|
||||
if (parameter.getParameterType().getName().equals("java.lang.Optional")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return annotation.required() //
|
||||
&& annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#verifyParameterValue(org.springframework.core.MethodParameter, java.lang.Object)
|
||||
@@ -247,8 +317,16 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
return annotation.required() && annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE)
|
||||
? super.verifyParameterValue(parameter, value) : value;
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (annotation.required()) {
|
||||
return annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE) ? SKIP_VALUE : null;
|
||||
}
|
||||
|
||||
return value == null ? SKIP_VALUE : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user