diff --git a/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java b/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java index 86c01450..82cba183 100644 --- a/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java +++ b/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java @@ -54,23 +54,6 @@ final class EncodingUtils { } } - /** - * 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 (Throwable e) { - throw new IllegalStateException(e); - } - } - /** * Encodes the given fragment value. * diff --git a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java index f9b7e5de..beb14285 100644 --- a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java +++ b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java @@ -17,7 +17,6 @@ package org.springframework.hateoas.server.core; import static org.springframework.hateoas.TemplateVariable.VariableType.*; import static org.springframework.hateoas.TemplateVariables.*; -import static org.springframework.hateoas.server.core.EncodingUtils.*; import static org.springframework.web.util.UriComponents.UriTemplateVariables.*; import java.lang.annotation.Annotation; @@ -30,6 +29,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; @@ -114,7 +114,9 @@ public class WebHandler { Iterator classMappingParameters = invocations.getObjectParameters(); while (classMappingParameters.hasNext()) { - values.put(names.next(), encodePath(classMappingParameters.next())); + String name = names.next(); + TemplateVariable variable = TemplateVariable.segment(name); + values.put(name, variable.prepareAndEncode(classMappingParameters.next())); } Method method = invocation.getMethod(); @@ -123,8 +125,8 @@ public class WebHandler { ConversionService resolved = conversionService; for (HandlerMethodParameter parameter : parameters.getParameterAnnotatedWith(PathVariable.class, arguments)) { - values.put(parameter.getVariableName(), - encodePath(parameter.getValueAsString(arguments, resolved))); + TemplateVariable variable = TemplateVariable.segment(parameter.getVariableName()); + values.put(variable.getName(), variable.prepareAndEncode(parameter.getValueAsString(arguments, resolved))); } List optionalEmptyParameters = new ArrayList<>(); @@ -194,24 +196,28 @@ public class WebHandler { if (value instanceof MultiValueMap) { - MultiValueMap requestParams = (MultiValueMap) value; + Map> requestParams = (Map>) value; - for (Map.Entry> multiValueEntry : requestParams.entrySet()) { - for (String singleEntryValue : multiValueEntry.getValue()) { - builder.queryParam(multiValueEntry.getKey(), encodeParameter(singleEntryValue)); + for (Entry> entry : requestParams.entrySet()) { + for (Object element : entry.getValue()) { + TemplateVariable variable = TemplateVariable.pathVariable(entry.getKey()); + builder.queryParam(entry.getKey(), variable.prepareAndEncode(element)); } } return; - } if (value instanceof Map) { - Map requestParams = (Map) value; + Map requestParams = (Map) value; - for (Map.Entry requestParamEntry : requestParams.entrySet()) { - builder.queryParam(requestParamEntry.getKey(), encodeParameter(requestParamEntry.getValue())); + for (Entry entry : requestParams.entrySet()) { + + String key = entry.getKey(); + TemplateVariable variable = TemplateVariable.requestParameter(key); + + builder.queryParam(key, variable.prepareAndEncode(entry.getValue())); } return; @@ -222,18 +228,17 @@ public class WebHandler { } String key = parameter.getVariableName(); + TemplateVariable variable = TemplateVariable.requestParameter(key); if (value instanceof Collection) { if (parameter.isNonComposite()) { - - TemplateVariable variable = TemplateVariable.requestParameter(key); builder.queryParam(key, variable.prepareAndEncode(value)); } else { for (Object element : (Collection) value) { if (key != null) { - builder.queryParam(key, encodeParameter(element)); + builder.queryParam(key, variable.prepareAndEncode(element)); } } } @@ -247,7 +252,7 @@ public class WebHandler { } else { if (key != null) { - builder.queryParam(key, encodeParameter(parameter.getValueAsString(arguments, conversionService))); + builder.queryParam(key, variable.prepareAndEncode(parameter.getValueAsString(arguments, conversionService))); } } } diff --git a/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderUnitTest.java index 831b16ff..7e147fcf 100644 --- a/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderUnitTest.java @@ -19,6 +19,8 @@ import static org.assertj.core.api.Assertions.*; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; import java.lang.reflect.Method; +import java.time.OffsetDateTime; +import java.time.ZoneId; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -28,6 +30,8 @@ import java.util.Optional; import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.NonComposite; @@ -638,6 +642,17 @@ class WebMvcLinkBuilderUnitTest extends TestUtils { assertThat(link.getHref()).endsWith("?foo=first,second"); } + @Test // #1485 + void encodesDatesCorrectly() { + + OffsetDateTime reference = OffsetDateTime.now(ZoneId.of("CET")); + Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithOffsetDateTime(reference)).withSelfRel(); + + assertThat(UriComponentsBuilder.fromUriString(link.getHref()).build().getQuery()) + .contains("%3A", "%2B") + .doesNotContain(":", "+"); + } + private static UriComponents toComponents(Link link) { return UriComponentsBuilder.fromUriString(link.expand().getHref()).build(); } @@ -733,6 +748,11 @@ class WebMvcLinkBuilderUnitTest extends TestUtils { HttpEntity nonCompositeRequestParam(@NonComposite @RequestParam("foo") Collection params) { return null; } + + @RequestMapping("/offset") + HttpEntity methodWithOffsetDateTime(@RequestParam @DateTimeFormat(iso = ISO.DATE_TIME) OffsetDateTime date) { + return null; + } } @RequestMapping("/parent")