#1588 - WebMvcLinkBuilder.linkTo(Method, Object[]) now properly handles request parameters.
We now route calls to WebMvcLinkBuilder.linkTo(Method, Object[]) through the same infrastructure that linkTo(…) calls with a dummy method invocation are routed through. We construct a fake LastInvocationAware pointing to the given Method and parameters.
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.server.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simple {@link MethodInvocation} implementation that can also be directly used as {@link LastInvocationAware}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
class DefaultMethodInvocation implements MethodInvocation, LastInvocationAware {
|
||||
|
||||
private final Class<?> type;
|
||||
private final Method method;
|
||||
private final Object[] arguments;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultMethodInvocation} for the given type, method and parameters.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
* @param arguments must not be {@literal null}.
|
||||
*/
|
||||
public DefaultMethodInvocation(Class<?> type, Method method, Object[] arguments) {
|
||||
|
||||
Assert.notNull(type, "targetType must not be null!");
|
||||
Assert.notNull(method, "method must not be null!");
|
||||
Assert.notNull(arguments, "arguments must not be null!");
|
||||
|
||||
this.type = type;
|
||||
this.method = method;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public DefaultMethodInvocation(Method method, Object[] arguments) {
|
||||
this(method.getDeclaringClass(), method, arguments);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.MethodInvocation#getTargetType()
|
||||
*/
|
||||
public Class<?> getTargetType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.MethodInvocation#getMethod()
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.MethodInvocation#getArguments()
|
||||
*/
|
||||
public Object[] getArguments() {
|
||||
return this.arguments;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.LastInvocationAware#getLastInvocation()
|
||||
*/
|
||||
@Override
|
||||
public MethodInvocation getLastInvocation() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.LastInvocationAware#getObjectParameters()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<Object> getObjectParameters() {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof DefaultMethodInvocation)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DefaultMethodInvocation that = (DefaultMethodInvocation) o;
|
||||
|
||||
return Objects.equals(this.type, that.type) //
|
||||
&& Objects.equals(this.method, that.method) //
|
||||
&& Arrays.equals(this.arguments, that.arguments);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = Objects.hash(this.type, this.method);
|
||||
result = 31 * result + Arrays.hashCode(this.arguments);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return "DefaultMethodInvocation(targetType=" + this.type //
|
||||
+ ", method=" + this.method //
|
||||
+ ", arguments=" + Arrays.deepToString(this.arguments) + ")";
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public class DummyInvocationUtils {
|
||||
return ReflectionUtils.invokeMethod(method, invocation.getThis(), invocation.getArguments());
|
||||
}
|
||||
|
||||
this.invocation = new SimpleMethodInvocation(targetType, method, invocation.getArguments());
|
||||
this.invocation = new DefaultMethodInvocation(targetType, method, invocation.getArguments());
|
||||
|
||||
Class<?> returnType = method.getReturnType();
|
||||
ClassLoader classLoader = method.getDeclaringClass().getClassLoader();
|
||||
@@ -144,7 +144,35 @@ public class DummyInvocationUtils {
|
||||
*/
|
||||
@Nullable
|
||||
public static LastInvocationAware getLastInvocationAware(Object source) {
|
||||
return (LastInvocationAware) ((Advised) source).getAdvisors()[0].getAdvice();
|
||||
|
||||
return (LastInvocationAware) (Advised.class.isInstance(source)
|
||||
? ((Advised) source).getAdvisors()[0].getAdvice()
|
||||
: source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple {@link LastInvocationAware} for the given method and parameters.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @since 1.3.4
|
||||
*/
|
||||
public static LastInvocationAware getLastInvocationAware(Method method, Object[] parameters) {
|
||||
return getLastInvocationAware(method.getDeclaringClass(), method, parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simple {@link LastInvocationAware} from the given type, method and parameters.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param method must not be {@literal null}.
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @since 1.3.4
|
||||
*/
|
||||
public static LastInvocationAware getLastInvocationAware(Class<?> type, Method method, Object[] parameters) {
|
||||
return new DefaultMethodInvocation(type, method, parameters);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -180,25 +208,31 @@ public class DummyInvocationUtils {
|
||||
return new CacheKey<T>(type, arguments);
|
||||
}
|
||||
|
||||
public Class<T> getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public Object[] getArguments() {
|
||||
return this.arguments;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
public boolean equals(@Nullable Object o) {
|
||||
|
||||
if (this == o)
|
||||
if (this == o) {
|
||||
return true;
|
||||
if (!(o instanceof CacheKey))
|
||||
}
|
||||
|
||||
if (!(o instanceof CacheKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CacheKey<?> cacheKey = (CacheKey<?>) o;
|
||||
return Objects.equals(this.type, cacheKey.type) && Arrays.equals(this.arguments, cacheKey.arguments);
|
||||
|
||||
return Objects.equals(this.type, cacheKey.type) //
|
||||
&& Arrays.equals(this.arguments, cacheKey.arguments);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
@@ -207,66 +241,16 @@ public class DummyInvocationUtils {
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
return "DummyInvocationUtils.CacheKey(type=" + this.type + ", arguments=" + Arrays.deepToString(this.arguments)
|
||||
return "DummyInvocationUtils.CacheKey(type=" + this.type //
|
||||
+ ", arguments=" + Arrays.deepToString(this.arguments) //
|
||||
+ ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SimpleMethodInvocation implements MethodInvocation {
|
||||
|
||||
private final Class<?> targetType;
|
||||
private final Method method;
|
||||
private final Object[] arguments;
|
||||
|
||||
public SimpleMethodInvocation(Class<?> targetType, Method method, Object[] arguments) {
|
||||
|
||||
Assert.notNull(targetType, "targetType must not be null!");
|
||||
Assert.notNull(method, "method must not be null!");
|
||||
Assert.notNull(arguments, "arguments must not be null!");
|
||||
|
||||
this.targetType = targetType;
|
||||
this.method = method;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public Class<?> getTargetType() {
|
||||
return this.targetType;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
public Object[] getArguments() {
|
||||
return this.arguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof SimpleMethodInvocation))
|
||||
return false;
|
||||
SimpleMethodInvocation that = (SimpleMethodInvocation) o;
|
||||
return Objects.equals(this.targetType, that.targetType) && Objects.equals(this.method, that.method)
|
||||
&& Arrays.equals(this.arguments, that.arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = Objects.hash(this.targetType, this.method);
|
||||
result = 31 * result + Arrays.hashCode(this.arguments);
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
return "DummyInvocationUtils.SimpleMethodInvocation(targetType=" + this.targetType + ", method=" + this.method
|
||||
+ ", arguments=" + Arrays.deepToString(this.arguments) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.springframework.hateoas.server.mvc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -27,12 +26,10 @@ import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.server.core.SpringAffordanceBuilder;
|
||||
import org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.UriTemplateFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.DefaultUriTemplateHandler;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Builder to ease building {@link Link} instances pointing to Spring MVC controllers.
|
||||
@@ -123,6 +120,10 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
return linkTo(method.getDeclaringClass(), method, parameters);
|
||||
}
|
||||
|
||||
@@ -133,12 +134,9 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
|
||||
|
||||
Assert.notNull(controller, "Controller type must not be null!");
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
String mapping = SpringAffordanceBuilder.DISCOVERER.getMapping(controller, method);
|
||||
UriTemplate template = UriTemplateFactory.templateFor(mapping);
|
||||
URI uri = template.expand(parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uri);
|
||||
return linkTo(DummyInvocationUtils.getLastInvocationAware(controller, method, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -618,6 +618,15 @@ class WebMvcLinkBuilderUnitTest extends TestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Test // #1588, #1589
|
||||
void buildsLinkFromMethodAndParameters() throws Exception {
|
||||
|
||||
Method method = ControllerWithMethods.class.getDeclaredMethod("methodWithRequestParam", String.class);
|
||||
|
||||
assertThat(linkTo(method, "someString").withSelfRel().getHref()).endsWith("?id=someString");
|
||||
assertThat(linkTo(method, new Object[] { null }).withSelfRel().getHref()).endsWith("?id={id}");
|
||||
}
|
||||
|
||||
private static UriComponents toComponents(Link link) {
|
||||
return UriComponentsBuilder.fromUriString(link.expand().getHref()).build();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user