diff --git a/pom.xml b/pom.xml index 9f4021fe..b639ed89 100644 --- a/pom.xml +++ b/pom.xml @@ -206,6 +206,13 @@ 1.9.0 test + + + joda-time + joda-time + 2.1 + test + diff --git a/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java b/src/main/java/org/springframework/hateoas/mvc/AnnotatedParametersParameterAccessor.java index dfdbc532..764a7810 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 the original author or authors. + * Copyright 2012-2013 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. @@ -16,44 +16,131 @@ package org.springframework.hateoas.mvc; import java.lang.annotation.Annotation; -import java.util.HashMap; -import java.util.Map; +import java.util.ArrayList; +import java.util.List; import org.aopalliance.intercept.MethodInvocation; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.hateoas.core.AnnotationAttribute; import org.springframework.hateoas.core.MethodParameters; import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import org.springframework.web.util.UriTemplate; /** + * Value object to allow accessing {@link MethodInvocation} parameters with the configured {@link AnnotationAttribute}. + * * @author Oliver Gierke */ -public class AnnotatedParametersParameterAccessor { +class AnnotatedParametersParameterAccessor { 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; } - public Map getBoundParameters(MethodInvocation invocation) { + /** + * Returns {@link BoundMethodParameter}s contained in the given {@link MethodInvocation}. + * + * @param invocation must not be {@literal null}. + * @return + */ + public List getBoundParameters(MethodInvocation invocation) { + + Assert.notNull(invocation, "MethodInvocation must not be null!"); MethodParameters parameters = new MethodParameters(invocation.getMethod()); Object[] arguments = invocation.getArguments(); - Map result = new HashMap(); + List result = new ArrayList(); for (MethodParameter parameter : parameters.getParametersWith(attribute.getAnnotationType())) { - - Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType()); - String annotationAttributeValue = attribute.getValueFrom(annotation); - String key = StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter - .getParameterName(); - result.put(key, arguments[parameter.getParameterIndex()]); + result.add(new BoundMethodParameter(parameter, arguments[parameter.getParameterIndex()], attribute)); } return result; } + + /** + * Represents a {@link MethodParameter} alongside the value it has been bound to. + * + * @author Oliver Gierke + */ + static class BoundMethodParameter { + + private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService(); + private static final TypeDescriptor STRING_DESCRIPTOR = TypeDescriptor.valueOf(String.class); + + private final MethodParameter parameter; + private final Object value; + private final AnnotationAttribute attribute; + private final TypeDescriptor parameterTypeDecsriptor; + + /** + * Creates a new {@link BoundMethodParameter} + * + * @param parameter + * @param value + * @param attribute + */ + public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) { + + Assert.notNull(parameter, "MethodParameter must not be null!"); + + this.parameter = parameter; + this.value = value; + this.attribute = attribute; + this.parameterTypeDecsriptor = TypeDescriptor.nested(parameter, 0); + } + + /** + * Returns the name of the {@link UriTemplate} variable to be bound. The name will be derived from the configured + * {@link AnnotationAttribute} or the {@link MethodParameter} name as fallback. + * + * @return + */ + public String getVariableName() { + + if (attribute == null) { + return parameter.getParameterName(); + } + + Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType()); + String annotationAttributeValue = attribute.getValueFrom(annotation); + return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName(); + } + + /** + * Returns the raw value bound to the {@link MethodParameter}. + * + * @return + */ + public Object getValue() { + return value; + } + + /** + * Returns the bound value converted into a {@link String} based on default conversion service setup. + * + * @return + */ + public String asString() { + + if (value == null) { + return null; + } + + return (String) CONVERSION_SERVICE.convert(value, parameterTypeDecsriptor, STRING_DESCRIPTOR); + } + } } diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java index f4fc0d14..10870ca2 100644 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactory.java @@ -24,7 +24,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.aopalliance.intercept.MethodInvocation; import org.springframework.core.MethodParameter; @@ -36,6 +35,7 @@ import org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware import org.springframework.hateoas.core.LinkBuilderSupport; import org.springframework.hateoas.core.MappingDiscoverer; import org.springframework.hateoas.core.MethodParameters; +import org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor.BoundMethodParameter; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -115,19 +115,21 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory param : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation).entrySet()) { + for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) { - Object value = param.getValue(); - String key = param.getKey(); + Object value = parameter.getValue(); + String key = parameter.getVariableName(); if (value instanceof Collection) { for (Object element : (Collection) value) { builder.queryParam(key, element); } } else { - builder.queryParam(key, value); + builder.queryParam(key, parameter.asString()); } } diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java index a3cd1f20..abfc9ec9 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java @@ -21,8 +21,12 @@ import static org.springframework.hateoas.core.DummyInvocationUtils.*; import java.util.Arrays; +import org.joda.time.DateTime; +import org.joda.time.format.ISODateTimeFormat; import org.junit.Test; import org.springframework.core.MethodParameter; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.hateoas.Link; import org.springframework.hateoas.TestUtils; import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.PersonControllerImpl; @@ -76,10 +80,26 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { assertThat(link.getHref(), endsWith("/sample/1?foo=value")); } + /** + * @see #57 + */ + @Test + public void usesDateTimeFormatForUriBinding() { + + DateTime now = DateTime.now(); + + ControllerLinkBuilderFactory factory = new ControllerLinkBuilderFactory(); + Link link = factory.linkTo(methodOn(SampleController.class).sampleMethod(now)).withSelfRel(); + assertThat(link.getHref(), endsWith("/sample/" + ISODateTimeFormat.date().print(now))); + } + static interface SampleController { @RequestMapping("/sample/{id}") HttpEntity sampleMethod(@PathVariable("id") Long id, SpecialType parameter); + + @RequestMapping("/sample/{time}") + HttpEntity sampleMethod(@PathVariable("time") @DateTimeFormat(iso = ISO.DATE) DateTime time); } static class SampleUriComponentsContributor implements UriComponentsContributor {