#1148 - Further performance improvements in link creation.
The implementation details of WebHandler have been significantly refactored to rather work with structures that allow better cacheability by clearly separating abstractions over the statically available information from the per-invocation aspects. This results in a new HandlerMethodParameter(s) abstraction within WebHandler. BoundMethodParameter has been removed entirely. HandlerMethodParameters are create once then cached for every controller method being linked to. DummyInvocationUtils now creates a ThreadLocal cache of the proxies created for calls to methodOn(…) as they essentially only act as basis for subsequent calls to the methods on the proxy created which in turn are expected to be handed into a linkTo(…) call which obtains the invocation right away. This avoids overhead in cases methodOn(…) is called multiple times for the same controller from a single controller. The lookup of the LastInvocationAware was previously routed through the proxy, handled by InvocationRecordingMethodInterceptor. This resulted in a second, reflective call for every link creation. DummyInvocationUtils now provides a dedicated lookup method as it knows about the structure of the proxy it created and thus can unfold the recorded invocation more effectively. The LinkBuilder type hierarchy now works with UriComponents and only creates a UriComponentsBuilder if it needs to modify the backing link in the first place. This avoids superfluous back and forth between UriComponents and UriComponentsBuilders that involved quite a bit of String parsing and creation. EncodingUtils now starts from a StandardCharsets.UTF_8 to avoid repeated Charset creation. The changes result in a ~3x performance compared to 1.0.2.RELEASE: 1.0.2.RELEASE Benchmark Mode Cnt Score Error Units ControllerLinkBuilderBenchmark.noLinkCreation thrpt 10 39004583,189 ± 751668,181 ops/s ControllerLinkBuilderBenchmark.pureLinkCreation thrpt 10 43443,133 ± 783,120 ops/s ControllerLinkBuilderBenchmark.withLinkCreation thrpt 10 60201,629 ± 1292,179 ops/s 1.1 / 1.0.3 SNAPSHOT Benchmark Mode Cnt Score Error Units ControllerLinkBuilderBenchmark.noLinkCreation thrpt 10 39618560,950 ± 612794,310 ops/s ControllerLinkBuilderBenchmark.pureLinkCreation thrpt 10 121700,634 ± 1510,415 ops/s ControllerLinkBuilderBenchmark.withLinkCreation thrpt 10 121982,085 ± 3344,206 ops/s noLinkCreation - creates a single RepresentationModel instance but adds no links pureLinkCreation - creates a single link pointing to a controller method withLinkCreation - creates a single RepresentationModel instance adding a single link
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
package org.springframework.hateoas.server.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -25,7 +24,7 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Simply helper to reference a dedicated attribute of an {@link Annotation}.
|
||||
*
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AnnotationAttribute {
|
||||
@@ -35,7 +34,7 @@ public class AnnotationAttribute {
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationAttribute} to the {@code value} attribute of the given {@link Annotation} type.
|
||||
*
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
*/
|
||||
public AnnotationAttribute(Class<? extends Annotation> annotationType) {
|
||||
@@ -44,7 +43,7 @@ public class AnnotationAttribute {
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationAttribute} for the given {@link Annotation} type and annotation attribute name.
|
||||
*
|
||||
*
|
||||
* @param annotationType must not be {@literal null}.
|
||||
* @param attributeName can be {@literal null}, defaults to {@code value}.
|
||||
*/
|
||||
@@ -58,7 +57,7 @@ public class AnnotationAttribute {
|
||||
|
||||
/**
|
||||
* Returns the annotation type.
|
||||
*
|
||||
*
|
||||
* @return the annotationType
|
||||
*/
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
@@ -67,7 +66,7 @@ public class AnnotationAttribute {
|
||||
|
||||
/**
|
||||
* Reads the {@link Annotation} attribute's value from the given {@link MethodParameter}.
|
||||
*
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@@ -75,27 +74,15 @@ public class AnnotationAttribute {
|
||||
public String getValueFrom(MethodParameter parameter) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the {@link Annotation} attribute's value from the given {@link AnnotatedElement}.
|
||||
*
|
||||
* @param annotatedElement must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public String getValueFrom(AnnotatedElement annotatedElement) {
|
||||
|
||||
Assert.notNull(annotatedElement, "Annotated element must not be null!");
|
||||
Annotation annotation = annotatedElement.getAnnotation(annotationType);
|
||||
return annotation == null ? null : getValueFrom(annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Annotation} attribute's value from the given {@link Annotation}.
|
||||
*
|
||||
*
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@@ -103,7 +90,9 @@ public class AnnotationAttribute {
|
||||
public String getValueFrom(Annotation annotation) {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
return (String) (attributeName == null ? AnnotationUtils.getValue(annotation) : AnnotationUtils.getValue(
|
||||
annotation, attributeName));
|
||||
|
||||
return (String) (attributeName == null //
|
||||
? AnnotationUtils.getValue(annotation) //
|
||||
: AnnotationUtils.getValue(annotation, attributeName));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,12 @@ import lombok.Value;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.aop.target.EmptyTargetSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -36,6 +39,8 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public class DummyInvocationUtils {
|
||||
|
||||
private static final ThreadLocal<Map<CacheKey<?>, Object>> CACHE = ThreadLocal.withInitial(HashMap::new);
|
||||
|
||||
/**
|
||||
* Method interceptor that records the last method invocation and creates a proxy for the return value that exposes
|
||||
* the method invocation.
|
||||
@@ -44,18 +49,10 @@ public class DummyInvocationUtils {
|
||||
*/
|
||||
private static class InvocationRecordingMethodInterceptor implements MethodInterceptor, LastInvocationAware {
|
||||
|
||||
private static final Method GET_INVOCATIONS;
|
||||
private static final Method GET_OBJECT_PARAMETERS;
|
||||
|
||||
private final Class<?> targetType;
|
||||
private final Object[] objectParameters;
|
||||
private MethodInvocation invocation;
|
||||
|
||||
static {
|
||||
GET_INVOCATIONS = ReflectionUtils.findMethod(LastInvocationAware.class, "getLastInvocation");
|
||||
GET_OBJECT_PARAMETERS = ReflectionUtils.findMethod(LastInvocationAware.class, "getObjectParameters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link InvocationRecordingMethodInterceptor} carrying the given parameters forward that might be
|
||||
* needed to populate the class level mapping.
|
||||
@@ -83,11 +80,7 @@ public class DummyInvocationUtils {
|
||||
|
||||
Method method = invocation.getMethod();
|
||||
|
||||
if (GET_INVOCATIONS.equals(method)) {
|
||||
return getLastInvocation();
|
||||
} else if (GET_OBJECT_PARAMETERS.equals(method)) {
|
||||
return getObjectParameters();
|
||||
} else if (ReflectionUtils.isObjectMethod(method)) {
|
||||
if (ReflectionUtils.isObjectMethod(method)) {
|
||||
return ReflectionUtils.invokeMethod(method, invocation.getThis(), invocation.getArguments());
|
||||
}
|
||||
|
||||
@@ -131,12 +124,29 @@ public class DummyInvocationUtils {
|
||||
* @param parameters parameters to extend template variables in the type level mapping.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T methodOn(Class<T> type, Object... parameters) {
|
||||
|
||||
Assert.notNull(type, "Given type must not be null!");
|
||||
|
||||
InvocationRecordingMethodInterceptor interceptor = new InvocationRecordingMethodInterceptor(type, parameters);
|
||||
return getProxyWithInterceptor(type, interceptor, type.getClassLoader());
|
||||
return (T) CACHE.get().computeIfAbsent(CacheKey.of(type, parameters), it -> {
|
||||
|
||||
InvocationRecordingMethodInterceptor interceptor = new InvocationRecordingMethodInterceptor(it.type,
|
||||
it.arguments);
|
||||
return getProxyWithInterceptor(it.type, interceptor, type.getClassLoader());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LastInvocationAware} instance from the given source, that essentially has to be a proxy created
|
||||
* via {@link #methodOn(Class, Object...)} and subsequent {@code linkTo(…)} calls.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public static LastInvocationAware getLastInvocationAware(Object source) {
|
||||
return (LastInvocationAware) ((Advised) source).getAdvisors()[0].getAdvice();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -157,6 +167,12 @@ public class DummyInvocationUtils {
|
||||
return (T) factory.getProxy(classLoader);
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
private static class CacheKey<T> {
|
||||
Class<T> type;
|
||||
Object[] arguments;
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class SimpleMethodInvocation implements MethodInvocation {
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ package org.springframework.hateoas.server.core;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
@@ -31,7 +34,7 @@ import org.springframework.web.util.UriUtils;
|
||||
@UtilityClass
|
||||
class EncodingUtils {
|
||||
|
||||
private static final String ENCODING = "UTF-8";
|
||||
private static final Charset ENCODING = StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* Encodes the given path value.
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
@@ -49,34 +48,28 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
*/
|
||||
public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkBuilder {
|
||||
|
||||
private final UriComponentsBuilder builder;
|
||||
private final @Getter List<Affordance> affordances;
|
||||
|
||||
private UriComponents components;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LinkBuilderSupport} using the given {@link UriComponents}.
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
protected LinkBuilderSupport(UriComponentsBuilder builder) {
|
||||
protected LinkBuilderSupport(UriComponents builder) {
|
||||
this(builder, Collections.emptyList());
|
||||
}
|
||||
|
||||
protected LinkBuilderSupport(UriComponentsBuilder builder, List<Affordance> affordances) {
|
||||
|
||||
Assert.notNull(builder, "UriComponentsBuilder must not be null!");
|
||||
Assert.notNull(affordances, "Affordances must not be null!");
|
||||
|
||||
this.builder = builder.cloneBuilder();
|
||||
this.affordances = affordances;
|
||||
}
|
||||
|
||||
protected LinkBuilderSupport(UriComponents components, List<Affordance> affordances) {
|
||||
|
||||
Assert.notNull(components, "UriComponents must not be null!");
|
||||
Assert.notNull(affordances, "Affordances must not be null!");
|
||||
|
||||
this.builder = UriComponentsBuilder.fromUriString(components.toUriString());
|
||||
// this.builder = UriComponentsBuilder.newInstance().uriComponents(components);
|
||||
this.affordances = affordances;
|
||||
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -108,20 +101,19 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
|
||||
protected T slash(UriComponents components, boolean encoded) {
|
||||
|
||||
return withFreshBuilder(builder -> {
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.newInstance().uriComponents(this.components);
|
||||
|
||||
for (String pathSegment : components.getPathSegments()) {
|
||||
builder.pathSegment(encoded ? pathSegment : encodePath(pathSegment));
|
||||
}
|
||||
for (String pathSegment : components.getPathSegments()) {
|
||||
builder.pathSegment(encoded ? pathSegment : encodePath(pathSegment));
|
||||
}
|
||||
|
||||
String fragment = components.getFragment();
|
||||
String fragment = components.getFragment();
|
||||
|
||||
if (fragment != null && !fragment.trim().isEmpty()) {
|
||||
builder.fragment(encoded ? fragment : encodeFragment(fragment));
|
||||
}
|
||||
if (fragment != null && !fragment.trim().isEmpty()) {
|
||||
builder.fragment(encoded ? fragment : encodeFragment(fragment));
|
||||
}
|
||||
|
||||
return createNewInstance(builder.query(components.getQuery()), affordances);
|
||||
});
|
||||
return createNewInstance(builder.query(components.getQuery()).build(), affordances);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -129,7 +121,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
* @see org.springframework.hateoas.LinkBuilder#toUri()
|
||||
*/
|
||||
public URI toUri() {
|
||||
return builder.build().toUri().normalize();
|
||||
return components.toUri().normalize();
|
||||
}
|
||||
|
||||
public T addAffordances(Collection<Affordance> affordances) {
|
||||
@@ -138,7 +130,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
newAffordances.addAll(this.affordances);
|
||||
newAffordances.addAll(affordances);
|
||||
|
||||
return createNewInstance(builder, newAffordances);
|
||||
return createNewInstance(components, newAffordances);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -165,20 +157,7 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return builder.build().toUriString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given {@link Function} using a freshly cloned {@link UriComponentsBuilder}.
|
||||
*
|
||||
* @param function must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected <S> S withFreshBuilder(Function<UriComponentsBuilder, S> function) {
|
||||
|
||||
Assert.notNull(function, "Function must not be null!");
|
||||
|
||||
return function.apply(builder.cloneBuilder());
|
||||
return components.toUriString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -194,5 +173,5 @@ public abstract class LinkBuilderSupport<T extends LinkBuilder> implements LinkB
|
||||
* @param builder will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected abstract T createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances);
|
||||
protected abstract T createNewInstance(UriComponents components, List<Affordance> affordances);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.util.List;
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A {@link LinkBuilderSupport} extension that can keep a list of {@link TemplateVariables} around.
|
||||
@@ -32,14 +31,6 @@ public abstract class TemplateVariableAwareLinkBuilderSupport<T extends Template
|
||||
|
||||
private final TemplateVariables variables;
|
||||
|
||||
protected TemplateVariableAwareLinkBuilderSupport(UriComponentsBuilder builder, TemplateVariables variables,
|
||||
List<Affordance> affordances) {
|
||||
|
||||
super(builder, affordances);
|
||||
|
||||
this.variables = variables;
|
||||
}
|
||||
|
||||
protected TemplateVariableAwareLinkBuilderSupport(UriComponents components, TemplateVariables variables,
|
||||
List<Affordance> affordances) {
|
||||
|
||||
@@ -50,14 +41,14 @@ public abstract class TemplateVariableAwareLinkBuilderSupport<T extends Template
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List)
|
||||
* @see org.springframework.hateoas.server.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponents, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected final T createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances) {
|
||||
return createNewInstance(builder, affordances, variables);
|
||||
protected final T createNewInstance(UriComponents components, List<Affordance> affordances) {
|
||||
return createNewInstance(components, affordances, variables);
|
||||
}
|
||||
|
||||
protected abstract T createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
protected abstract T createNewInstance(UriComponents components, List<Affordance> affordances,
|
||||
TemplateVariables variables);
|
||||
|
||||
/*
|
||||
|
||||
@@ -20,21 +20,23 @@ import static org.springframework.hateoas.TemplateVariables.*;
|
||||
import static org.springframework.hateoas.server.core.EncodingUtils.*;
|
||||
import static org.springframework.web.util.UriComponents.UriTemplateVariables.*;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -47,6 +49,7 @@ import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -62,15 +65,12 @@ import org.springframework.web.util.UriTemplate;
|
||||
* Utility for taking a method invocation and extracting a {@link LinkBuilder}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
public class WebHandler {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer
|
||||
.of(new AnnotationMappingDiscoverer(RequestMapping.class));
|
||||
private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR //
|
||||
= new AnnotatedParametersParameterAccessor(new AnnotationAttribute(PathVariable.class));
|
||||
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR //
|
||||
= new RequestParamParameterAccessor();
|
||||
|
||||
private static final Map<AffordanceKey, List<Affordance>> AFFORDANCES_CACHE = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
@@ -78,49 +78,68 @@ public class WebHandler {
|
||||
T createBuilder(UriComponents components, TemplateVariables variables, List<Affordance> affordances);
|
||||
}
|
||||
|
||||
public static <T extends LinkBuilder> Function<Function<String, UriComponentsBuilder>, T> linkTo(
|
||||
Object invocationValue, LinkBuilderCreator<T> creator) {
|
||||
return linkTo(invocationValue, creator, null);
|
||||
public interface PreparedWebHandler<T extends LinkBuilder> {
|
||||
T conclude(Function<String, UriComponentsBuilder> finisher);
|
||||
}
|
||||
|
||||
public static <T extends LinkBuilder> Function<Function<String, UriComponentsBuilder>, T> linkTo(
|
||||
Object invocationValue, LinkBuilderCreator<T> creator,
|
||||
public static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
|
||||
LinkBuilderCreator<T> creator) {
|
||||
return linkTo(invocationValue, creator,
|
||||
(BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder>) null);
|
||||
}
|
||||
|
||||
public static <T extends LinkBuilder> T linkTo(Object invocationValue, LinkBuilderCreator<T> creator,
|
||||
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler,
|
||||
Function<String, UriComponentsBuilder> finisher) {
|
||||
|
||||
return linkTo(invocationValue, creator, additionalUriHandler).conclude(finisher);
|
||||
}
|
||||
|
||||
private static <T extends LinkBuilder> PreparedWebHandler<T> linkTo(Object invocationValue,
|
||||
LinkBuilderCreator<T> creator,
|
||||
@Nullable BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
|
||||
|
||||
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
|
||||
|
||||
LastInvocationAware invocations = (LastInvocationAware) invocationValue;
|
||||
LastInvocationAware invocations = (LastInvocationAware) DummyInvocationUtils
|
||||
.getLastInvocationAware(invocationValue);
|
||||
|
||||
if (invocations == null) {
|
||||
throw new IllegalStateException(String.format("Could not obtain previous invocation from %s!", invocationValue));
|
||||
}
|
||||
|
||||
MethodInvocation invocation = invocations.getLastInvocation();
|
||||
|
||||
return mappingToUriComponentsBuilder -> {
|
||||
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), invocation.getMethod());
|
||||
|
||||
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), invocation.getMethod());
|
||||
return finisher -> {
|
||||
|
||||
UriComponentsBuilder builder = mappingToUriComponentsBuilder.apply(mapping);
|
||||
UriComponentsBuilder builder = finisher.apply(mapping);
|
||||
UriTemplate template = UriTemplateFactory.templateFor(mapping == null ? "/" : mapping);
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
|
||||
Iterator<String> names = template.getVariableNames().iterator();
|
||||
List<String> variableNames = template.getVariableNames();
|
||||
Iterator<String> names = variableNames.iterator();
|
||||
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
|
||||
|
||||
while (classMappingParameters.hasNext()) {
|
||||
values.put(names.next(), encodePath(classMappingParameters.next()));
|
||||
}
|
||||
|
||||
for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR
|
||||
.getBoundParameters(invocation)) {
|
||||
HandlerMethodParameters parameters = HandlerMethodParameters.of(invocation.getMethod());
|
||||
Object[] arguments = invocation.getArguments();
|
||||
|
||||
values.put(parameter.getVariableName(), encodePath(parameter.asString()));
|
||||
for (HandlerMethodParameter parameter : parameters.getParameterAnnotatedWith(PathVariable.class, arguments)) {
|
||||
values.put(parameter.getVariableName(), encodePath(parameter.getValueAsString(arguments)));
|
||||
}
|
||||
|
||||
List<String> optionalEmptyParameters = new ArrayList<>();
|
||||
|
||||
for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR
|
||||
.getBoundParameters(invocation)) {
|
||||
for (HandlerMethodParameter parameter : parameters.getParameterAnnotatedWith(RequestParam.class, arguments)) {
|
||||
|
||||
bindRequestParameters(builder, parameter);
|
||||
bindRequestParameters(builder, parameter, arguments);
|
||||
|
||||
if (SKIP_VALUE.equals(parameter.getValue())) {
|
||||
if (SKIP_VALUE.equals(parameter.getVerifiedValue(arguments))) {
|
||||
|
||||
values.put(parameter.getVariableName(), SKIP_VALUE);
|
||||
|
||||
@@ -130,14 +149,14 @@ public class WebHandler {
|
||||
}
|
||||
}
|
||||
|
||||
for (String variable : template.getVariableNames()) {
|
||||
for (String variable : variableNames) {
|
||||
if (!values.containsKey(variable)) {
|
||||
values.put(variable, SKIP_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
UriComponents components = additionalUriHandler == null //
|
||||
? builder.buildAndExpand(values)
|
||||
? builder.buildAndExpand(values) //
|
||||
: additionalUriHandler.apply(builder, invocation).buildAndExpand(values);
|
||||
|
||||
TemplateVariables variables = NONE;
|
||||
@@ -150,11 +169,9 @@ public class WebHandler {
|
||||
variables = variables.concat(variable);
|
||||
}
|
||||
|
||||
String href = components.toUriString().equals("") ? "/" : components.toUriString();
|
||||
|
||||
List<Affordance> affordances = AFFORDANCES_CACHE.computeIfAbsent(
|
||||
AffordanceKey.of(invocation.getTargetType(), invocation.getMethod(), href),
|
||||
key -> SpringAffordanceBuilder.create(key.type, key.method, key.href, DISCOVERER));
|
||||
AffordanceKey.of(invocation.getTargetType(), invocation.getMethod(), components),
|
||||
key -> SpringAffordanceBuilder.create(key.type, key.method, key.href.toUriString(), DISCOVERER));
|
||||
|
||||
return creator.createBuilder(components, variables, affordances);
|
||||
};
|
||||
@@ -168,10 +185,15 @@ public class WebHandler {
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void bindRequestParameters(UriComponentsBuilder builder,
|
||||
AnnotatedParametersParameterAccessor.BoundMethodParameter parameter) {
|
||||
private static void bindRequestParameters(UriComponentsBuilder builder, HandlerMethodParameter parameter,
|
||||
Object[] arguments) {
|
||||
|
||||
Object value = parameter.getVerifiedValue(arguments);
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object value = parameter.getValue();
|
||||
String key = parameter.getVariableName();
|
||||
|
||||
if (value instanceof MultiValueMap) {
|
||||
@@ -204,74 +226,254 @@ public class WebHandler {
|
||||
|
||||
if (parameter.isRequired()) {
|
||||
if (key != null) {
|
||||
builder.queryParam(key, String.format("{%s}", parameter.getVariableName()));
|
||||
builder.queryParam(key, String.format("{%s}", key));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (key != null) {
|
||||
builder.queryParam(key, encodeParameter(parameter.asString()));
|
||||
builder.queryParam(key, encodeParameter(parameter.getValueAsString(arguments)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom extension of {@link AnnotatedParametersParameterAccessor} for {@link RequestParam} to allow {@literal null}
|
||||
* values handed in for optional request parameters.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class RequestParamParameterAccessor extends AnnotatedParametersParameterAccessor {
|
||||
@Value(staticConstructor = "of")
|
||||
private static class AffordanceKey {
|
||||
|
||||
public RequestParamParameterAccessor() {
|
||||
super(new AnnotationAttribute(RequestParam.class));
|
||||
Class<?> type;
|
||||
Method method;
|
||||
UriComponents href;
|
||||
}
|
||||
|
||||
private static class HandlerMethodParameters {
|
||||
|
||||
private static final List<Class<? extends Annotation>> ANNOTATIONS = Arrays.asList(RequestParam.class,
|
||||
PathVariable.class);
|
||||
private static final Map<Method, HandlerMethodParameters> CACHE = new ConcurrentHashMap<Method, HandlerMethodParameters>();
|
||||
|
||||
private final MultiValueMap<Class<? extends Annotation>, HandlerMethodParameter> byAnnotationCache;
|
||||
|
||||
private HandlerMethodParameters(MethodParameters parameters) {
|
||||
|
||||
this.byAnnotationCache = new LinkedMultiValueMap<>();
|
||||
|
||||
for (Class<? extends Annotation> annotation : ANNOTATIONS) {
|
||||
|
||||
this.byAnnotationCache.putAll(parameters.getParametersWith(annotation).stream() //
|
||||
.map(it -> HandlerMethodParameter.of(it, annotation)) //
|
||||
.collect(Collectors.groupingBy(HandlerMethodParameter::getAnnotationType, LinkedMultiValueMap::new,
|
||||
Collectors.toList())));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#createParameter(org.springframework.core.MethodParameter, java.lang.Object, org.springframework.hateoas.core.AnnotationAttribute)
|
||||
*/
|
||||
@Override
|
||||
protected BoundMethodParameter createParameter(final MethodParameter parameter, @Nullable Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
public static HandlerMethodParameters of(Method method) {
|
||||
|
||||
return new BoundMethodParameter(parameter, value, attribute) {
|
||||
return CACHE.computeIfAbsent(method, it -> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor.BoundMethodParameter#isRequired()
|
||||
*/
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
MethodParameters parameters = MethodParameters.of(it);
|
||||
return new HandlerMethodParameters(parameters);
|
||||
});
|
||||
}
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
public List<HandlerMethodParameter> getParameterAnnotatedWith(Class<? extends Annotation> annotation,
|
||||
Object[] arguments) {
|
||||
|
||||
if (parameter.isOptional()) {
|
||||
return false;
|
||||
}
|
||||
List<HandlerMethodParameter> parameters = byAnnotationCache.get(annotation);
|
||||
|
||||
return annotation != null && annotation.required() //
|
||||
&& annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE);
|
||||
if (parameters == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<HandlerMethodParameter> result = new ArrayList<>();
|
||||
|
||||
for (HandlerMethodParameter parameter : parameters) {
|
||||
if (parameter.getVerifiedValue(arguments) != null) {
|
||||
result.add(parameter);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class HandlerMethodParameter {
|
||||
|
||||
private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService();
|
||||
private static final TypeDescriptor STRING_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final Map<Class<? extends Annotation>, Function<MethodParameter, ? extends HandlerMethodParameter>> FACTORY;
|
||||
private static final String NO_PARAMETER_NAME = "Could not determine name of parameter %s! Make sure you compile with parameter information or explicitly define a parameter name in %s.";
|
||||
|
||||
static {
|
||||
FACTORY = new HashMap<>();
|
||||
FACTORY.put(RequestParam.class, RequestParamParameter::new);
|
||||
FACTORY.put(PathVariable.class, PathVariableParameter::new);
|
||||
}
|
||||
|
||||
private final MethodParameter parameter;
|
||||
private final AnnotationAttribute attribute;
|
||||
private final TypeDescriptor typeDescriptor;
|
||||
|
||||
private String variableName;
|
||||
|
||||
/**
|
||||
* Creates a new {@link HandlerMethodParameter} for the given {@link MethodParameter} and
|
||||
* {@link AnnotationAttribute}.
|
||||
*
|
||||
* @param parameter
|
||||
* @param attribute
|
||||
*/
|
||||
private HandlerMethodParameter(MethodParameter parameter, AnnotationAttribute attribute) {
|
||||
|
||||
this.parameter = parameter;
|
||||
this.attribute = attribute;
|
||||
|
||||
int nestingIndex = Optional.class.isAssignableFrom(parameter.getParameterType()) ? 1 : 0;
|
||||
|
||||
this.typeDescriptor = TypeDescriptor.nested(parameter, nestingIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link HandlerMethodParameter} for the given {@link MethodParameter} and annotation type.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static HandlerMethodParameter of(MethodParameter parameter, Class<? extends Annotation> type) {
|
||||
|
||||
Function<MethodParameter, ? extends HandlerMethodParameter> function = FACTORY.get(type);
|
||||
|
||||
if (function == null) {
|
||||
throw new IllegalArgumentException(String.format("Unsupported annotation type %s!", type.getName()));
|
||||
}
|
||||
|
||||
return function.apply(parameter);
|
||||
}
|
||||
|
||||
Class<? extends Annotation> getAnnotationType() {
|
||||
return attribute.getAnnotationType();
|
||||
}
|
||||
|
||||
public String getVariableName() {
|
||||
|
||||
if (variableName == null) {
|
||||
this.variableName = determineVariableName();
|
||||
}
|
||||
|
||||
return variableName;
|
||||
}
|
||||
|
||||
public String getValueAsString(Object[] values) {
|
||||
|
||||
Object value = values[parameter.getParameterIndex()];
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Cannot turn null value into required String!");
|
||||
}
|
||||
|
||||
if (String.class.isInstance(value)) {
|
||||
return (String) value;
|
||||
}
|
||||
|
||||
value = ObjectUtils.unwrapOptional(value);
|
||||
|
||||
Object result = CONVERSION_SERVICE.convert(value, typeDescriptor, STRING_DESCRIPTOR);
|
||||
|
||||
if (result == null) {
|
||||
throw new IllegalArgumentException(String.format("Conversion of value %s resulted in null!", value));
|
||||
}
|
||||
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
private String determineVariableName() {
|
||||
|
||||
if (attribute == null) {
|
||||
|
||||
this.variableName = parameter.getParameterName();
|
||||
|
||||
return variableName;
|
||||
}
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String parameterName = annotation != null ? attribute.getValueFrom(annotation) : "";
|
||||
|
||||
if (parameterName != null && StringUtils.hasText(parameterName)) {
|
||||
return parameterName;
|
||||
}
|
||||
|
||||
parameterName = parameter.getParameterName();
|
||||
|
||||
if (parameterName == null) {
|
||||
throw new IllegalStateException(String.format(NO_PARAMETER_NAME, parameter, attribute.getAnnotationType()));
|
||||
}
|
||||
|
||||
return parameterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value for the underlying {@link MethodParameter} potentially applying validation.
|
||||
*
|
||||
* @param values must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public Object getVerifiedValue(Object[] values) {
|
||||
return values[parameter.getParameterIndex()];
|
||||
}
|
||||
|
||||
public abstract boolean isRequired();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodParameter} implementation to work with {@link RequestParam}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
private static class RequestParamParameter extends HandlerMethodParameter {
|
||||
|
||||
private final MethodParameter parameter;
|
||||
|
||||
public RequestParamParameter(MethodParameter parameter) {
|
||||
|
||||
super(parameter, new AnnotationAttribute(RequestParam.class));
|
||||
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor#verifyParameterValue(org.springframework.core.MethodParameter, java.lang.Object)
|
||||
* @see org.springframework.hateoas.server.core.WebHandler.HandlerMethodParameter#isRequired()
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
protected Object verifyParameterValue(MethodParameter parameter, @Nullable Object value) {
|
||||
public boolean isRequired() {
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
|
||||
value = ObjectUtils.unwrapOptional(value);
|
||||
if (parameter.isOptional()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return annotation != null && annotation.required() //
|
||||
&& annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.WebHandler.HandlerMethodParameter#verifyValue(java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getVerifiedValue(Object[] values) {
|
||||
|
||||
Object value = ObjectUtils.unwrapOptional(values[parameter.getParameterIndex()]);
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class);
|
||||
|
||||
if (!(annotation != null && annotation.required()) || parameter.isOptional()) {
|
||||
return SKIP_VALUE;
|
||||
}
|
||||
@@ -281,171 +483,23 @@ public class WebHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to allow accessing {@link MethodInvocation} parameters with the configured
|
||||
* {@link AnnotationAttribute}.
|
||||
* {@link HandlerMethodParameter} extension dealing with {@link PathVariable} parameters.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class AnnotatedParametersParameterAccessor {
|
||||
private static class PathVariableParameter extends HandlerMethodParameter {
|
||||
|
||||
private final @NonNull AnnotationAttribute attribute;
|
||||
|
||||
/**
|
||||
* Returns {@link BoundMethodParameter}s contained in the given {@link MethodInvocation}.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<BoundMethodParameter> getBoundParameters(MethodInvocation invocation) {
|
||||
|
||||
Assert.notNull(invocation, "MethodInvocation must not be null!");
|
||||
|
||||
MethodParameters parameters = MethodParameters.of(invocation.getMethod());
|
||||
Object[] arguments = invocation.getArguments();
|
||||
List<BoundMethodParameter> result = new ArrayList<>();
|
||||
|
||||
for (MethodParameter parameter : parameters.getParametersWith(attribute.getAnnotationType())) {
|
||||
|
||||
Object value = arguments[parameter.getParameterIndex()];
|
||||
Object verifiedValue = verifyParameterValue(parameter, value);
|
||||
|
||||
if (verifiedValue != null) {
|
||||
result.add(createParameter(parameter, verifiedValue, attribute));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
public PathVariableParameter(MethodParameter parameter) {
|
||||
super(parameter, new AnnotationAttribute(PathVariable.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link BoundMethodParameter} for the given {@link MethodParameter}, parameter value and
|
||||
* {@link AnnotationAttribute}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
* @param attribute must not be {@literal null}.
|
||||
* @return
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.WebHandler.HandlerMethodParameter#isRequired()
|
||||
*/
|
||||
protected BoundMethodParameter createParameter(MethodParameter parameter, @Nullable Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
return new BoundMethodParameter(parameter, value, attribute);
|
||||
@Override
|
||||
public boolean isRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to verify the parameter values given for a dummy invocation. Default implementation rejects
|
||||
* {@literal null} values as they indicate an invalid dummy call.
|
||||
*
|
||||
* @param parameter will never be {@literal null}.
|
||||
* @param value could be {@literal null}.
|
||||
* @return the verified value.
|
||||
*/
|
||||
@Nullable
|
||||
protected Object verifyParameterValue(MethodParameter parameter, @Nullable Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 parameterTypeDescriptor;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BoundMethodParameter}
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param value can be {@literal null}.
|
||||
* @param attribute can be {@literal null}.
|
||||
*/
|
||||
public BoundMethodParameter(MethodParameter parameter, @Nullable Object value, AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
|
||||
boolean isOptionalWrapper = Optional.class.isAssignableFrom(parameter.getParameterType());
|
||||
|
||||
this.parameter = parameter;
|
||||
this.value = value;
|
||||
this.attribute = attribute;
|
||||
this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, isOptionalWrapper ? 1 : 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
|
||||
*/
|
||||
@Nullable
|
||||
public String getVariableName() {
|
||||
|
||||
if (attribute == null) {
|
||||
return parameter.getParameterName();
|
||||
}
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String annotationAttributeValue = annotation != null ? attribute.getValueFrom(annotation) : "";
|
||||
|
||||
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw value bound to the {@link MethodParameter}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound value converted into a {@link String} based on default conversion service setup.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
|
||||
Object value = this.value;
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalStateException("Cannot turn null value into required String!");
|
||||
}
|
||||
|
||||
Object result = CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
|
||||
|
||||
if (result == null) {
|
||||
throw new IllegalStateException(String.format("Conversion of value %s resulted in null!", value));
|
||||
}
|
||||
|
||||
return (String) result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given parameter is a required one. Defaults to {@literal true}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
private static class AffordanceKey {
|
||||
|
||||
Class<?> type;
|
||||
Method method;
|
||||
String href;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.server.LinkBuilder;
|
||||
import org.springframework.hateoas.server.core.LinkBuilderSupport;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -35,12 +36,12 @@ public class BasicLinkBuilder extends LinkBuilderSupport<BasicLinkBuilder> {
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
private BasicLinkBuilder(UriComponentsBuilder builder) {
|
||||
super(builder);
|
||||
private BasicLinkBuilder(UriComponents components) {
|
||||
super(components);
|
||||
}
|
||||
|
||||
private BasicLinkBuilder(UriComponentsBuilder builder, List<Affordance> affordances) {
|
||||
super(builder, affordances);
|
||||
private BasicLinkBuilder(UriComponents components, List<Affordance> affordances) {
|
||||
super(components, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,16 +50,16 @@ public class BasicLinkBuilder extends LinkBuilderSupport<BasicLinkBuilder> {
|
||||
* @return
|
||||
*/
|
||||
public static BasicLinkBuilder linkToCurrentMapping() {
|
||||
return new BasicLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping());
|
||||
return new BasicLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping().build());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.server.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List)
|
||||
* @see org.springframework.hateoas.server.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponents, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected BasicLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances) {
|
||||
return new BasicLinkBuilder(builder, affordances);
|
||||
protected BasicLinkBuilder createNewInstance(UriComponents components, List<Affordance> affordances) {
|
||||
return new BasicLinkBuilder(components, affordances);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -63,16 +63,12 @@ public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSuppo
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
ControllerLinkBuilder(UriComponentsBuilder builder) {
|
||||
this(builder, TemplateVariables.NONE, Collections.emptyList());
|
||||
ControllerLinkBuilder(UriComponents components) {
|
||||
this(components, TemplateVariables.NONE, Collections.emptyList());
|
||||
}
|
||||
|
||||
ControllerLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(builder, variables, affordances);
|
||||
}
|
||||
|
||||
ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(uriComponents, variables, affordances);
|
||||
ControllerLinkBuilder(UriComponents components, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(components, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +100,7 @@ public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSuppo
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
|
||||
return new ControllerLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,7 +122,7 @@ public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSuppo
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uriComponents, true);
|
||||
return new ControllerLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -147,7 +143,7 @@ public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSuppo
|
||||
UriTemplate template = UriTemplateFactory.templateFor(DISCOVERER.getMapping(controller, method));
|
||||
URI uri = template.expand(parameters);
|
||||
|
||||
return new ControllerLinkBuilder(getBuilder()).slash(uri);
|
||||
return new ControllerLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,9 +221,9 @@ public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSuppo
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected ControllerLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
protected ControllerLinkBuilder createNewInstance(UriComponents components, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new ControllerLinkBuilder(builder, variables, affordances);
|
||||
return new ControllerLinkBuilder(components, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,7 +120,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<Co
|
||||
}
|
||||
|
||||
return builder;
|
||||
}).apply(mapping -> ControllerLinkBuilder.getBuilder().path(mapping));
|
||||
}, mapping -> ControllerLinkBuilder.getBuilder().path(mapping));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
@@ -56,6 +57,10 @@ class UriComponentsBuilderFactory {
|
||||
: cacheBaseUri(ServletUriComponentsBuilder.fromCurrentServletMapping());
|
||||
}
|
||||
|
||||
public static UriComponents getComponents() {
|
||||
return getBuilder().build();
|
||||
}
|
||||
|
||||
private static RequestAttributes getRequestAttributes() {
|
||||
|
||||
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
|
||||
@@ -61,12 +61,8 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
*/
|
||||
WebMvcLinkBuilder(UriComponentsBuilder builder) {
|
||||
this(builder, TemplateVariables.NONE, Collections.emptyList());
|
||||
}
|
||||
|
||||
WebMvcLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(builder, variables, affordances);
|
||||
WebMvcLinkBuilder(UriComponents components) {
|
||||
this(components, TemplateVariables.NONE, Collections.emptyList());
|
||||
}
|
||||
|
||||
WebMvcLinkBuilder(UriComponents uriComponents, TemplateVariables variables, List<Affordance> affordances) {
|
||||
@@ -102,7 +98,7 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uriComponents, true);
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,7 +120,7 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uriComponents, true);
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -146,7 +142,7 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
|
||||
UriTemplate template = UriTemplateFactory.templateFor(mapping);
|
||||
URI uri = template.expand(parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uri);
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getComponents()).slash(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,12 +217,12 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<W
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
* @see org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponents, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected WebMvcLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
protected WebMvcLinkBuilder createNewInstance(UriComponents components, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new WebMvcLinkBuilder(builder, variables, affordances);
|
||||
return new WebMvcLinkBuilder(components, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -115,14 +115,16 @@ public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory<WebMvc
|
||||
|
||||
Object parameterValue = parameterValues.next();
|
||||
|
||||
uriComponentsContributors.stream() //
|
||||
.filter(it -> it.supportsParameter(parameter)) //
|
||||
.forEach(it -> it.enhance(builder, parameter, parameterValue));
|
||||
for (UriComponentsContributor contributor : uriComponentsContributors) {
|
||||
if (contributor.supportsParameter(parameter)) {
|
||||
contributor.enhance(builder, parameter, parameterValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder;
|
||||
|
||||
}).apply(builderFactory);
|
||||
}, builderFactory);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -18,11 +18,11 @@ package org.springframework.hateoas.server.reactive;
|
||||
import static org.springframework.web.filter.reactive.ServerWebExchangeContextFilter.*;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
@@ -31,6 +31,7 @@ import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.server.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.server.core.WebHandler;
|
||||
import org.springframework.hateoas.server.core.WebHandler.PreparedWebHandler;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -46,10 +47,6 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
*/
|
||||
public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<WebFluxLinkBuilder> {
|
||||
|
||||
private WebFluxLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(builder, variables, affordances);
|
||||
}
|
||||
|
||||
private WebFluxLinkBuilder(UriComponents components, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(components, variables, affordances);
|
||||
}
|
||||
@@ -89,17 +86,18 @@ public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> controller, Object... parameters) {
|
||||
|
||||
return DummyInvocationUtils.methodOn(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
* @see org.springframework.hateoas.server.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponents, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected WebFluxLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
protected WebFluxLinkBuilder createNewInstance(UriComponents components, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new WebFluxLinkBuilder(builder, variables, affordances);
|
||||
return new WebFluxLinkBuilder(components, variables, affordances);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -246,11 +244,9 @@ public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<
|
||||
|
||||
private static Mono<WebFluxLinkBuilder> linkToInternal(Object invocation, Mono<UriComponentsBuilder> exchange) {
|
||||
|
||||
Function<Function<String, UriComponentsBuilder>, WebFluxLinkBuilder> linkTo = //
|
||||
WebHandler.linkTo(invocation, WebFluxLinkBuilder::new);
|
||||
PreparedWebHandler<WebFluxLinkBuilder> handler = WebHandler.linkTo(invocation, WebFluxLinkBuilder::new);
|
||||
|
||||
return exchange.map(WebFluxLinkBuilder::getBuilderCreator) //
|
||||
.map(linkTo);
|
||||
return exchange.map(WebFluxLinkBuilder::getBuilderCreator).map(handler::conclude);
|
||||
}
|
||||
|
||||
private static Function<String, UriComponentsBuilder> getBuilderCreator(UriComponentsBuilder exchange) {
|
||||
|
||||
Reference in New Issue
Block a user