diff --git a/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java b/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java index 45ed083b..8d985660 100644 --- a/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java +++ b/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -149,10 +149,12 @@ class AnnotatedParametersParameterAccessor { Assert.notNull(parameter, "MethodParameter must not be null!"); + boolean isOptional = Java8Utils.isJava8Optional(parameter.getParameterType()); + this.parameter = parameter; this.value = value; this.attribute = attribute; - this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, 0); + this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, isOptional ? 1 : 0); } /** diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java index 3a48f4d2..e21e0bb7 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -299,7 +299,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory type) { + + Assert.notNull(type, "Type must not be null!"); + + return OPTIONAL_PRESENT && OptionalValueAccessor.isOptional(type); + } + + /** + * Returns whether the given source value is a JDK 8 {@link Optional}. + * + * @param source can be {@literal null}. + * @return + */ + static boolean isJava8Optional(Object source) { + return OPTIONAL_PRESENT && OptionalValueAccessor.isOptional(source); + } + + /** + * Unwraps the value contained in a JDK 8 {@link Optional} if the value is one. + * + * @param source can be {@literal null}. + * @return + */ + static Object unwrapJava8Optional(Object source) { + return OPTIONAL_PRESENT && isJava8Optional(source) ? OptionalValueAccessor.unwrapOptional(source) : source; + } + + private static class OptionalValueAccessor { + + static boolean isOptional(Class type) { + return Optional.class.isAssignableFrom(type); + } + + static boolean isOptional(Object source) { + return Optional.class.isInstance(source); + } + + static Object unwrapOptional(Object source) { + return ((Optional) source).orElse(null); + } + } +} diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java index ab16a086..b5a09f3a 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderUnitTest.java @@ -22,6 +22,7 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.hamcrest.Matchers; import org.junit.Rule; @@ -554,6 +555,31 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { assertThat(linkTo(PersonControllerImpl.class).withSelfRel().getHref(), endsWith("/ctx/people")); } + /** + * @see #639 + */ + @Test + public void considersEmptyOptionalMethodParameterOptional() { + + Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithJdk8Optional(Optional. empty())) + .withSelfRel(); + + assertThat(link.isTemplated(), is(true)); + assertThat(link.getVariableNames(), contains("value")); + } + + /** + * @see #639 + */ + @Test + public void considersOptionalWithValueMethodParameterOptional() { + + Link link = linkTo(methodOn(ControllerWithMethods.class).methodWithJdk8Optional(Optional.of(1))).withSelfRel(); + + assertThat(link.isTemplated(), is(false)); + assertThat(link.getHref(), endsWith("?value=1")); + } + private static UriComponents toComponents(Link link) { return UriComponentsBuilder.fromUriString(link.expand().getHref()).build(); } @@ -630,6 +656,10 @@ public class ControllerLinkBuilderUnitTest extends TestUtils { HttpEntity methodForOptionalSizeWithDefaultValue(@RequestParam(defaultValue = "10") Integer size) { return null; } + + HttpEntity methodWithJdk8Optional(@RequestParam Optional value) { + return null; + } } @RequestMapping("/parent")