From ce71b144a1f210c963d0ab7c4929136cc6eddc42 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Wed, 21 Jul 2021 09:46:48 +0200 Subject: [PATCH] #1588 - WebMvcLinkBuilder.linkTo(Method, Object[]) now properly handles request parameters. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../server/core/DefaultMethodInvocation.java | 147 ++++++++++++++++++ .../server/core/DummyInvocationUtils.java | 126 +++++++-------- .../hateoas/server/mvc/WebMvcLinkBuilder.java | 14 +- .../server/mvc/WebMvcLinkBuilderUnitTest.java | 9 ++ 4 files changed, 217 insertions(+), 79 deletions(-) create mode 100644 src/main/java/org/springframework/hateoas/server/core/DefaultMethodInvocation.java diff --git a/src/main/java/org/springframework/hateoas/server/core/DefaultMethodInvocation.java b/src/main/java/org/springframework/hateoas/server/core/DefaultMethodInvocation.java new file mode 100644 index 00000000..47fff462 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/server/core/DefaultMethodInvocation.java @@ -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 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) + ")"; + } +} diff --git a/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java b/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java index ba691c87..07a10bd7 100644 --- a/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java +++ b/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java @@ -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(type, arguments); } - public Class 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) + ")"; - } - } } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java index 2d8b59a8..455d8e20 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java @@ -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