diff --git a/src/main/java/org/springframework/hateoas/config/LinkBuilderBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/LinkBuilderBeanDefinitionRegistrar.java index dc51a4ab..edd89a7f 100644 --- a/src/main/java/org/springframework/hateoas/config/LinkBuilderBeanDefinitionRegistrar.java +++ b/src/main/java/org/springframework/hateoas/config/LinkBuilderBeanDefinitionRegistrar.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. @@ -83,9 +83,12 @@ public class LinkBuilderBeanDefinitionRegistrar implements ImportBeanDefinitionR private static BeanDefinitionBuilder getEntityControllerLinksFor(Class type, Class> linkBuilderFactoryType) { + RootBeanDefinition definition = new RootBeanDefinition(linkBuilderFactoryType); + definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ControllerEntityLinksFactoryBean.class); builder.addPropertyValue("annotation", type); - builder.addPropertyValue("linkBuilderFactory", new RootBeanDefinition(linkBuilderFactoryType)); + builder.addPropertyValue("linkBuilderFactory", definition); return builder; } diff --git a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java index d51397a1..88e66d7c 100755 --- a/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/mvc/ControllerLinkBuilder.java @@ -17,23 +17,16 @@ package org.springframework.hateoas.mvc; import java.lang.reflect.Method; import java.net.URI; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; import javax.servlet.http.HttpServletRequest; -import org.aopalliance.intercept.MethodInvocation; import org.springframework.hateoas.Link; -import org.springframework.hateoas.core.AnnotationAttribute; import org.springframework.hateoas.core.AnnotationMappingDiscoverer; import org.springframework.hateoas.core.DummyInvocationUtils; -import org.springframework.hateoas.core.DummyInvocationUtils.LastInvocationAware; import org.springframework.hateoas.core.LinkBuilderSupport; import org.springframework.hateoas.core.MappingDiscoverer; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -50,15 +43,14 @@ import org.springframework.web.util.UriTemplate; public class ControllerLinkBuilder extends LinkBuilderSupport { private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class); - private static final AnnotatedParametersParameterAccessor accessor = new AnnotatedParametersParameterAccessor( - new AnnotationAttribute(PathVariable.class)); + private static final ControllerLinkBuilderFactory FACTORY = new ControllerLinkBuilderFactory(); /** * Creates a new {@link ControllerLinkBuilder} using the given {@link UriComponentsBuilder}. * * @param builder must not be {@literal null}. */ - private ControllerLinkBuilder(UriComponentsBuilder builder) { + ControllerLinkBuilder(UriComponentsBuilder builder) { super(builder); } @@ -127,27 +119,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport classMappingParameters = invocations.getObjectParameters(); - Method method = invocation.getMethod(); - - UriTemplate template = new UriTemplate(DISCOVERER.getMapping(method)); - Map values = new HashMap(); - - if (classMappingParameters.hasNext()) { - for (String variable : template.getVariableNames()) { - values.put(variable, classMappingParameters.next()); - } - } - - values.putAll(accessor.getBoundParameters(invocation)); - URI uri = template.expand(values); - - return new ControllerLinkBuilder(getBuilder()).slash(uri); + return FACTORY.linkTo(invocationValue); } /** @@ -186,7 +158,7 @@ public class ControllerLinkBuilder extends LinkBuilderSupport { + private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class); + private static final AnnotatedParametersParameterAccessor ACCESSOR = new AnnotatedParametersParameterAccessor( + new AnnotationAttribute(PathVariable.class)); + + private List uriComponentsContributors = new ArrayList(); + + /** + * Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method + * invocations. + * + * @see #linkTo(Object) + * @param uriComponentsContributors the uriComponentsContributors to set + */ + public void setUriComponentsContributors(List uriComponentsContributors) { + this.uriComponentsContributors = Collections.unmodifiableList(uriComponentsContributors); + } + /* * (non-Javadoc) * @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class) @@ -51,8 +89,29 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory classMappingParameters = invocations.getObjectParameters(); + Method method = invocation.getMethod(); + + UriTemplate template = new UriTemplate(DISCOVERER.getMapping(method)); + Map values = new HashMap(); + + if (classMappingParameters.hasNext()) { + for (String variable : template.getVariableNames()) { + values.put(variable, classMappingParameters.next()); + } + } + + values.putAll(ACCESSOR.getBoundParameters(invocation)); + URI uri = template.expand(values); + + UriComponentsBuilder builder = ControllerLinkBuilder.getBuilder(); + return new ControllerLinkBuilder(applyUriComponentsContributer(builder, invocation)).slash(uri); } /* @@ -63,4 +122,28 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory parameterValues = Arrays.asList(invocation.getArguments()).iterator(); + + for (MethodParameter parameter : parameters.getParameters()) { + Object parameterValue = parameterValues.next(); + for (UriComponentsContributor contributor : uriComponentsContributors) { + if (contributor.supportsParameter(parameter)) { + contributor.enhance(builder, parameter, parameterValue); + } + } + } + + return builder; + } } diff --git a/src/main/java/org/springframework/hateoas/mvc/UriComponentsContributor.java b/src/main/java/org/springframework/hateoas/mvc/UriComponentsContributor.java new file mode 100644 index 00000000..74743d1a --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mvc/UriComponentsContributor.java @@ -0,0 +1,49 @@ +/* + * Copyright 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. + * 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.mvc; + +import org.springframework.core.MethodParameter; +import org.springframework.hateoas.MethodLinkBuilderFactory; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * SPI callback to enhance a {@link UriComponentsBuilder} when referring to a method through a dummy method invocation. + * Will usually be implemented in implementations of {@link HandlerMethodArgumentResolver} as they represent exactly the + * same functionality inverted. + * + * @see MethodLinkBuilderFactory#linkTo(Object) + * @author Oliver Gierke + */ +public interface UriComponentsContributor { + + /** + * Returns whether the {@link UriComponentsBuilder} supports the given {@link MethodParameter}. + * + * @param parameter will never be {@literal null}. + * @return + */ + boolean supportsParameter(MethodParameter parameter); + + /** + * Enhance the given {@link UriComponentsBuilder} with the given value. + * + * @param builder will never be {@literal null}. + * @param parameter will never be {@literal null}. + * @param value can be {@literal null}. + */ + void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value); +} diff --git a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java index ab935f70..18cce51a 100644 --- a/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mvc/ControllerLinkBuilderFactoryUnitTest.java @@ -17,12 +17,20 @@ package org.springframework.hateoas.mvc; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.springframework.hateoas.core.DummyInvocationUtils.*; + +import java.util.Arrays; import org.junit.Test; +import org.springframework.core.MethodParameter; import org.springframework.hateoas.Link; import org.springframework.hateoas.TestUtils; import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.PersonControllerImpl; import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.PersonsAddressesController; +import org.springframework.http.HttpEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.util.UriComponentsBuilder; /** * Unit tests for {@link ControllerLinkBuilderFactory}. @@ -51,4 +59,40 @@ public class ControllerLinkBuilderFactoryUnitTest extends TestUtils { assertThat(link.getRel(), is(Link.REL_SELF)); assertThat(link.getHref(), endsWith("/people/15/addresses")); } + + @Test + public void appliesParameterValueIfContributorConfigured() { + + ControllerLinkBuilderFactory factory = new ControllerLinkBuilderFactory(); + factory.setUriComponentsContributors(Arrays.asList(new SampleUriComponentsContributor())); + + SpecialType specialType = new SpecialType(); + specialType.parameterValue = "value"; + + Link link = factory.linkTo(methodOn(SampleController.class).sampleMethod(1L, specialType)).withSelfRel(); + assertThat(link.getHref(), endsWith("/sample/1?foo=value")); + } + + static interface SampleController { + + @RequestMapping("/sample/{id}") + HttpEntity sampleMethod(@PathVariable("id") Long id, SpecialType parameter); + } + + static class SampleUriComponentsContributor implements UriComponentsContributor { + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return SpecialType.class.equals(parameter.getParameterType()); + } + + @Override + public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Object value) { + builder.queryParam("foo", ((SpecialType) value).parameterValue); + } + } + + static class SpecialType { + String parameterValue; + } }