diff --git a/src/main/java/org/springframework/hateoas/server/core/AnnotationAttribute.java b/src/main/java/org/springframework/hateoas/server/core/AnnotationAttribute.java index 72fd9526..4c511ee5 100644 --- a/src/main/java/org/springframework/hateoas/server/core/AnnotationAttribute.java +++ b/src/main/java/org/springframework/hateoas/server/core/AnnotationAttribute.java @@ -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 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 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)); } } 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 3659ae77..eb815155 100644 --- a/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java +++ b/src/main/java/org/springframework/hateoas/server/core/DummyInvocationUtils.java @@ -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, 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 methodOn(Class 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 { + Class type; + Object[] arguments; + } + @Value private static class SimpleMethodInvocation implements MethodInvocation { diff --git a/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java b/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java index 00bdd783..b11e03a5 100644 --- a/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java +++ b/src/main/java/org/springframework/hateoas/server/core/EncodingUtils.java @@ -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. diff --git a/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java index 9b508a48..36f00828 100644 --- a/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java +++ b/src/main/java/org/springframework/hateoas/server/core/LinkBuilderSupport.java @@ -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 implements LinkBuilder { - private final UriComponentsBuilder builder; private final @Getter List 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 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 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 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 implements LinkB * @see org.springframework.hateoas.LinkBuilder#toUri() */ public URI toUri() { - return builder.build().toUri().normalize(); + return components.toUri().normalize(); } public T addAffordances(Collection affordances) { @@ -138,7 +130,7 @@ public abstract class LinkBuilderSupport 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 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 withFreshBuilder(Function 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 implements LinkB * @param builder will never be {@literal null}. * @return */ - protected abstract T createNewInstance(UriComponentsBuilder builder, List affordances); + protected abstract T createNewInstance(UriComponents components, List affordances); } diff --git a/src/main/java/org/springframework/hateoas/server/core/TemplateVariableAwareLinkBuilderSupport.java b/src/main/java/org/springframework/hateoas/server/core/TemplateVariableAwareLinkBuilderSupport.java index a456fbc5..dc036ad2 100644 --- a/src/main/java/org/springframework/hateoas/server/core/TemplateVariableAwareLinkBuilderSupport.java +++ b/src/main/java/org/springframework/hateoas/server/core/TemplateVariableAwareLinkBuilderSupport.java @@ -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 affordances) { - - super(builder, affordances); - - this.variables = variables; - } - protected TemplateVariableAwareLinkBuilderSupport(UriComponents components, TemplateVariables variables, List affordances) { @@ -50,14 +41,14 @@ public abstract class TemplateVariableAwareLinkBuilderSupport affordances) { - return createNewInstance(builder, affordances, variables); + protected final T createNewInstance(UriComponents components, List affordances) { + return createNewInstance(components, affordances, variables); } - protected abstract T createNewInstance(UriComponentsBuilder builder, List affordances, + protected abstract T createNewInstance(UriComponents components, List affordances, TemplateVariables variables); /* diff --git a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java index 961cb072..e62c0285 100644 --- a/src/main/java/org/springframework/hateoas/server/core/WebHandler.java +++ b/src/main/java/org/springframework/hateoas/server/core/WebHandler.java @@ -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> AFFORDANCES_CACHE = new ConcurrentReferenceHashMap<>(); @@ -78,49 +78,68 @@ public class WebHandler { T createBuilder(UriComponents components, TemplateVariables variables, List affordances); } - public static Function, T> linkTo( - Object invocationValue, LinkBuilderCreator creator) { - return linkTo(invocationValue, creator, null); + public interface PreparedWebHandler { + T conclude(Function finisher); } - public static Function, T> linkTo( - Object invocationValue, LinkBuilderCreator creator, + public static PreparedWebHandler linkTo(Object invocationValue, + LinkBuilderCreator creator) { + return linkTo(invocationValue, creator, + (BiFunction) null); + } + + public static T linkTo(Object invocationValue, LinkBuilderCreator creator, + @Nullable BiFunction additionalUriHandler, + Function finisher) { + + return linkTo(invocationValue, creator, additionalUriHandler).conclude(finisher); + } + + private static PreparedWebHandler linkTo(Object invocationValue, + LinkBuilderCreator creator, @Nullable BiFunction 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 values = new HashMap<>(); - Iterator names = template.getVariableNames().iterator(); + List variableNames = template.getVariableNames(); + Iterator names = variableNames.iterator(); Iterator 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 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 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> ANNOTATIONS = Arrays.asList(RequestParam.class, + PathVariable.class); + private static final Map CACHE = new ConcurrentHashMap(); + + private final MultiValueMap, HandlerMethodParameter> byAnnotationCache; + + private HandlerMethodParameters(MethodParameters parameters) { + + this.byAnnotationCache = new LinkedMultiValueMap<>(); + + for (Class 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 getParameterAnnotatedWith(Class annotation, + Object[] arguments) { - if (parameter.isOptional()) { - return false; - } + List parameters = byAnnotationCache.get(annotation); - return annotation != null && annotation.required() // - && annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE); + if (parameters == null) { + return Collections.emptyList(); + } + + List 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, Function> 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 type) { + + Function function = FACTORY.get(type); + + if (function == null) { + throw new IllegalArgumentException(String.format("Unsupported annotation type %s!", type.getName())); + } + + return function.apply(parameter); + } + + Class 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 getBoundParameters(MethodInvocation invocation) { - - Assert.notNull(invocation, "MethodInvocation must not be null!"); - - MethodParameters parameters = MethodParameters.of(invocation.getMethod()); - Object[] arguments = invocation.getArguments(); - List 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; } } diff --git a/src/main/java/org/springframework/hateoas/server/mvc/BasicLinkBuilder.java b/src/main/java/org/springframework/hateoas/server/mvc/BasicLinkBuilder.java index ab6176c9..8df572d9 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/BasicLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/BasicLinkBuilder.java @@ -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 { * * @param builder must not be {@literal null}. */ - private BasicLinkBuilder(UriComponentsBuilder builder) { - super(builder); + private BasicLinkBuilder(UriComponents components) { + super(components); } - private BasicLinkBuilder(UriComponentsBuilder builder, List affordances) { - super(builder, affordances); + private BasicLinkBuilder(UriComponents components, List affordances) { + super(components, affordances); } /** @@ -49,16 +50,16 @@ public class BasicLinkBuilder extends LinkBuilderSupport { * @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 affordances) { - return new BasicLinkBuilder(builder, affordances); + protected BasicLinkBuilder createNewInstance(UriComponents components, List affordances) { + return new BasicLinkBuilder(components, affordances); } /* diff --git a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilder.java b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilder.java index c0e7ff47..d566809b 100755 --- a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilder.java @@ -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 affordances) { - super(builder, variables, affordances); - } - - ControllerLinkBuilder(UriComponents uriComponents, TemplateVariables variables, List affordances) { - super(uriComponents, variables, affordances); + ControllerLinkBuilder(UriComponents components, TemplateVariables variables, List 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 affordances, + protected ControllerLinkBuilder createNewInstance(UriComponents components, List affordances, TemplateVariables variables) { - return new ControllerLinkBuilder(builder, variables, affordances); + return new ControllerLinkBuilder(components, variables, affordances); } /** diff --git a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java index e3c309fa..9ef8736b 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/ControllerLinkBuilderFactory.java @@ -120,7 +120,7 @@ public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory ControllerLinkBuilder.getBuilder().path(mapping)); + }, mapping -> ControllerLinkBuilder.getBuilder().path(mapping)); } /* diff --git a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java index 20436d23..35e01642 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/UriComponentsBuilderFactory.java @@ -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(); 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 a43b03a0..644d140e 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.java @@ -61,12 +61,8 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport affordances) { - super(builder, variables, affordances); + WebMvcLinkBuilder(UriComponents components) { + this(components, TemplateVariables.NONE, Collections.emptyList()); } WebMvcLinkBuilder(UriComponents uriComponents, TemplateVariables variables, List affordances) { @@ -102,7 +98,7 @@ public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport affordances, + protected WebMvcLinkBuilder createNewInstance(UriComponents components, List affordances, TemplateVariables variables) { - return new WebMvcLinkBuilder(builder, variables, affordances); + return new WebMvcLinkBuilder(components, variables, affordances); } /** diff --git a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java index 1f297ef1..eabe7723 100644 --- a/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java +++ b/src/main/java/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.java @@ -115,14 +115,16 @@ public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory 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); } /* diff --git a/src/main/java/org/springframework/hateoas/server/reactive/WebFluxLinkBuilder.java b/src/main/java/org/springframework/hateoas/server/reactive/WebFluxLinkBuilder.java index 62039a84..fcb0d901 100644 --- a/src/main/java/org/springframework/hateoas/server/reactive/WebFluxLinkBuilder.java +++ b/src/main/java/org/springframework/hateoas/server/reactive/WebFluxLinkBuilder.java @@ -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 { - private WebFluxLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List affordances) { - super(builder, variables, affordances); - } - private WebFluxLinkBuilder(UriComponents components, TemplateVariables variables, List affordances) { super(components, variables, affordances); } @@ -89,17 +86,18 @@ public class WebFluxLinkBuilder extends TemplateVariableAwareLinkBuilderSupport< * @return */ public static T methodOn(Class 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 affordances, + protected WebFluxLinkBuilder createNewInstance(UriComponents components, List 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 linkToInternal(Object invocation, Mono exchange) { - Function, WebFluxLinkBuilder> linkTo = // - WebHandler.linkTo(invocation, WebFluxLinkBuilder::new); + PreparedWebHandler handler = WebHandler.linkTo(invocation, WebFluxLinkBuilder::new); - return exchange.map(WebFluxLinkBuilder::getBuilderCreator) // - .map(linkTo); + return exchange.map(WebFluxLinkBuilder::getBuilderCreator).map(handler::conclude); } private static Function getBuilderCreator(UriComponentsBuilder exchange) { diff --git a/src/test/java/org/springframework/hateoas/server/core/LinkBuilderSupportUnitTest.java b/src/test/java/org/springframework/hateoas/server/core/LinkBuilderSupportUnitTest.java index daa1d24e..05a41b74 100755 --- a/src/test/java/org/springframework/hateoas/server/core/LinkBuilderSupportUnitTest.java +++ b/src/test/java/org/springframework/hateoas/server/core/LinkBuilderSupportUnitTest.java @@ -23,6 +23,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.hateoas.Affordance; import org.springframework.hateoas.TestUtils; +import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; /** @@ -36,14 +37,16 @@ class LinkBuilderSupportUnitTest extends TestUtils { @Test void callingSlashWithEmptyStringIsNoOp() { - SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance(), Collections.emptyList()); + SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance().build(), + Collections.emptyList()); assertThat(builder.slash("")).isEqualTo(builder); } @Test void appendsFragmentCorrectly() { - SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance(), Collections.emptyList()); + SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance().build(), + Collections.emptyList()); builder = builder.slash("foo#bar"); assertThat(builder.toString()).endsWith("foo#bar"); builder = builder.slash("bar"); @@ -62,7 +65,8 @@ class LinkBuilderSupportUnitTest extends TestUtils { @Test void appendsPathContainingColonsCorrectly() { - SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance(), Collections.emptyList()); + SampleLinkBuilder builder = new SampleLinkBuilder(UriComponentsBuilder.newInstance().build(), + Collections.emptyList()); builder = builder.slash("47:11"); @@ -71,8 +75,8 @@ class LinkBuilderSupportUnitTest extends TestUtils { static class SampleLinkBuilder extends LinkBuilderSupport { - public SampleLinkBuilder(UriComponentsBuilder builder, List afforances) { - super(builder, afforances); + public SampleLinkBuilder(UriComponents components, List afforances) { + super(components, afforances); } @Override @@ -85,8 +89,8 @@ class LinkBuilderSupportUnitTest extends TestUtils { * @see org.springframework.hateoas.core.LinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List) */ @Override - protected SampleLinkBuilder createNewInstance(UriComponentsBuilder builder, List affordances) { - return new SampleLinkBuilder(builder, affordances); + protected SampleLinkBuilder createNewInstance(UriComponents components, List affordances) { + return new SampleLinkBuilder(components, affordances); } } }