#728 - API polishing.
Renamed LinkBuilder implementations for the different web stacks Web(MVC|Flux)LinkBuilder for symmetry. Introduced dedicated builder types to be used from WebFluxLinkBuilder to more conveniently integrate with reactive flows (see the changes in WebFluxEmployeeController). Deprecated ControllerLinkBuilder(Factory) in favor of WebMvcLinkBuilder(Factory). Generally untangled the use of WebMvcLinkBuilder from the reactive examples.
This commit is contained in:
@@ -18,7 +18,7 @@ package org.springframework.hateoas;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
|
||||
import org.springframework.hateoas.mvc.WebMvcLinkBuilder;
|
||||
|
||||
/**
|
||||
* Extension of {@link LinkBuilderFactory} for implementations that also support creating {@link LinkBuilder}s by
|
||||
@@ -52,9 +52,9 @@ public interface MethodLinkBuilderFactory<T extends LinkBuilder> extends LinkBui
|
||||
/**
|
||||
* Returns a {@link LinkBuilder} pointing to the URI mapped to the method the result is handed into this method. Use
|
||||
* {@link DummyInvocationUtils#methodOn(Class, Object...)} to obtain a dummy instance of a controller to record a
|
||||
* dummy method invocation on. See {@link ControllerLinkBuilder#linkTo(Object)} for an example.
|
||||
*
|
||||
* @see ControllerLinkBuilder#linkTo(Object)
|
||||
* dummy method invocation on. See {@link WebMvcLinkBuilder#linkTo(Object)} for an example.
|
||||
*
|
||||
* @see WebMvcLinkBuilder#linkTo(Object)
|
||||
* @param methodInvocationResult must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.hateoas.EntityLinks;
|
||||
import org.springframework.hateoas.core.ControllerEntityLinksFactoryBean;
|
||||
import org.springframework.hateoas.core.DelegatingEntityLinks;
|
||||
import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory;
|
||||
import org.springframework.hateoas.mvc.WebMvcLinkBuilderFactory;
|
||||
import org.springframework.plugin.core.PluginRegistry;
|
||||
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
|
||||
import org.springframework.stereotype.Controller;
|
||||
@@ -54,7 +54,7 @@ class EntityLinksConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
ControllerEntityLinksFactoryBean controllerEntityLinks(ControllerLinkBuilderFactory controllerLinkBuilderFactory) {
|
||||
ControllerEntityLinksFactoryBean controllerEntityLinks(WebMvcLinkBuilderFactory controllerLinkBuilderFactory) {
|
||||
|
||||
ControllerEntityLinksFactoryBean factory = new ControllerEntityLinksFactoryBean();
|
||||
factory.setAnnotation(Controller.class);
|
||||
@@ -64,7 +64,7 @@ class EntityLinksConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
ControllerLinkBuilderFactory controllerLinkBuilderFactoryBean() {
|
||||
return new ControllerLinkBuilderFactory();
|
||||
WebMvcLinkBuilderFactory webMvcLinkBuilderFactoryBean() {
|
||||
return new WebMvcLinkBuilderFactory();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mvc;
|
||||
package org.springframework.hateoas.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springframework.web.util.UriTemplate;
|
||||
* @author Michal Stochmialek
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
class UriTemplateFactory {
|
||||
public class UriTemplateFactory {
|
||||
|
||||
private static final Map<String, UriTemplate> CACHE = new ConcurrentReferenceHashMap<String, UriTemplate>();
|
||||
|
||||
@@ -42,7 +42,6 @@ import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.LinkBuilder;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
@@ -57,13 +56,14 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Utility for taking a method invocation and extracting a {@link ControllerLinkBuilder}.
|
||||
* Utility for taking a method invocation and extracting a {@link LinkBuilder}.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class WebHandler {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class);
|
||||
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 //
|
||||
@@ -90,7 +90,7 @@ public class WebHandler {
|
||||
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), invocation.getMethod());
|
||||
|
||||
UriComponentsBuilder builder = mappingToUriComponentsBuilder.apply(mapping);
|
||||
UriTemplate template = new UriTemplate(mapping == null ? "/" : mapping);
|
||||
UriTemplate template = UriTemplateFactory.templateFor(mapping == null ? "/" : mapping);
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
|
||||
Iterator<String> names = template.getVariableNames().iterator();
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.hateoas.core.CachingMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.core.MappingDiscoverer;
|
||||
import org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.core.UriTemplateFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
@@ -47,7 +48,9 @@ import org.springframework.web.util.UriTemplate;
|
||||
* @author Andrew Naydyonock
|
||||
* @author Oliver Trosien
|
||||
* @author Greg Turnquist
|
||||
* @deprecated use {@link WebMvcLinkBuilder} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ControllerLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<ControllerLinkBuilder> {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer
|
||||
|
||||
@@ -42,7 +42,9 @@ import org.springframework.hateoas.core.WebHandler;
|
||||
* @author Kevin Conaway
|
||||
* @author Andrew Naydyonock
|
||||
* @author Greg Turnquist
|
||||
* @deprecated use {@link WebMvcLinkBuilderFactory} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<ControllerLinkBuilder> {
|
||||
|
||||
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<>();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.hateoas.mvc;
|
||||
|
||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
|
||||
import static org.springframework.hateoas.mvc.WebMvcLinkBuilder.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.hateoas.mvc;
|
||||
|
||||
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
|
||||
import static org.springframework.hateoas.mvc.WebMvcLinkBuilder.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mvc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.CachingMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.core.MappingDiscoverer;
|
||||
import org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.core.UriTemplateFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.util.DefaultUriTemplateHandler;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Builder to ease building {@link Link} instances pointing to Spring MVC controllers.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Kamill Sokol
|
||||
* @author Greg Turnquist
|
||||
* @author Kevin Conaway
|
||||
* @author Andrew Naydyonock
|
||||
* @author Oliver Trosien
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class WebMvcLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<WebMvcLinkBuilder> {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = CachingMappingDiscoverer
|
||||
.of(new AnnotationMappingDiscoverer(RequestMapping.class));
|
||||
private static final WebMvcLinkBuilderFactory FACTORY = new WebMvcLinkBuilderFactory();
|
||||
private static final CustomUriTemplateHandler HANDLER = new CustomUriTemplateHandler();
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} using the given {@link UriComponentsBuilder}.
|
||||
*
|
||||
* @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 uriComponents, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(uriComponents, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} with a base of the mapping annotated to the given controller class.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller) {
|
||||
return linkTo(controller, new Object[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} with a base of the mapping annotated to the given controller class. The
|
||||
* additional parameters are used to fill up potentially available path variables in the class scop request mapping.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
|
||||
* {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller, Object... parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebMvcLinkBuilder} with a base of the mapping annotated to the given controller class.
|
||||
* Parameter map is used to fill up potentially available path variables in the class scope request mapping.
|
||||
*
|
||||
* @param controller the class to discover the annotation on, must not be {@literal null}.
|
||||
* @param parameters additional parameters to bind to the URI template declared in the annotation, must not be
|
||||
* {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller must not be null!");
|
||||
Assert.notNull(parameters, "Parameters must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller);
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(mapping == null ? "/" : mapping);
|
||||
UriComponents uriComponents = HANDLER.expandAndEncode(builder, parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uriComponents, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Method, Object...)
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
|
||||
return linkTo(method.getDeclaringClass(), method, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(Class<?>, Method, Object...)
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
|
||||
|
||||
Assert.notNull(controller, "Controller type must not be null!");
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
|
||||
String mapping = DISCOVERER.getMapping(controller, method);
|
||||
UriTemplate template = UriTemplateFactory.templateFor(mapping);
|
||||
URI uri = template.expand(parameters);
|
||||
|
||||
return new WebMvcLinkBuilder(UriComponentsBuilderFactory.getBuilder()).slash(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link WebMvcLinkBuilder} pointing to a controller method. Hand in a dummy method invocation result you
|
||||
* can create via {@link #methodOn(Class, Object...)} or {@link DummyInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* <pre>
|
||||
* @RequestMapping("/customers")
|
||||
* class CustomerController {
|
||||
*
|
||||
* @RequestMapping("/{id}/addresses")
|
||||
* HttpEntity<Addresses> showAddresses(@PathVariable Long id) { … }
|
||||
* }
|
||||
*
|
||||
* Link link = linkTo(methodOn(CustomerController.class).showAddresses(2L)).withRel("addresses");
|
||||
* </pre>
|
||||
*
|
||||
* The resulting {@link Link} instance will point to {@code /customers/2/addresses} and have a rel of
|
||||
* {@code addresses}. For more details on the method invocation constraints, see
|
||||
* {@link DummyInvocationUtils#methodOn(Class, Object...)}.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @return
|
||||
*/
|
||||
public static WebMvcLinkBuilder linkTo(Object invocationValue) {
|
||||
return FACTORY.linkTo(invocationValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a {@link Link} from the {@link WebMvcLinkBuilder} and look up the related {@link Affordance}. Should only
|
||||
* be one.
|
||||
*
|
||||
* <pre>
|
||||
* Link findOneLink = linkTo(methodOn(EmployeeController.class).findOne(id)).withSelfRel()
|
||||
* .andAffordance(afford(methodOn(EmployeeController.class).updateEmployee(null, id)));
|
||||
* </pre>
|
||||
*
|
||||
* This takes a link and adds an {@link Affordance} based on another Spring MVC handler method.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @return
|
||||
*/
|
||||
public static Affordance afford(Object invocationValue) {
|
||||
|
||||
WebMvcLinkBuilder linkBuilder = linkTo(invocationValue);
|
||||
|
||||
Assert.isTrue(linkBuilder.getAffordances().size() == 1, "A base can only have one affordance, itself");
|
||||
|
||||
return linkBuilder.getAffordances().get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
|
||||
* imports of {@link WebMvcLinkBuilder}.
|
||||
*
|
||||
* @param controller must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level mapping.
|
||||
* @return
|
||||
*/
|
||||
public static <T> T methodOn(Class<T> controller, Object... parameters) {
|
||||
return DummyInvocationUtils.methodOn(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.UriComponentsLinkBuilder#getThis()
|
||||
*/
|
||||
@Override
|
||||
protected WebMvcLinkBuilder getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected WebMvcLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new WebMvcLinkBuilder(builder, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} to continue to build the already built URI in a more fine grained way.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public UriComponentsBuilder toUriComponentsBuilder() {
|
||||
return UriComponentsBuilder.fromUri(toUri());
|
||||
}
|
||||
|
||||
private static class CustomUriTemplateHandler extends DefaultUriTemplateHandler {
|
||||
|
||||
public CustomUriTemplateHandler() {
|
||||
setStrictEncoding(true);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.util.DefaultUriTemplateHandler#expandAndEncode(org.springframework.web.util.UriComponentsBuilder, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
|
||||
return super.expandAndEncode(builder, uriVariables);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.mvc;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.MethodLinkBuilderFactory;
|
||||
import org.springframework.hateoas.core.LinkBuilderSupport;
|
||||
import org.springframework.hateoas.core.MethodParameters;
|
||||
import org.springframework.hateoas.core.WebHandler;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller.
|
||||
*
|
||||
* @author Ricardo Gladwell
|
||||
* @author Oliver Gierke
|
||||
* @author Dietrich Schulten
|
||||
* @author Kamill Sokol
|
||||
* @author Ross Turner
|
||||
* @author Oemer Yildiz
|
||||
* @author Kevin Conaway
|
||||
* @author Andrew Naydyonock
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class WebMvcLinkBuilderFactory implements MethodLinkBuilderFactory<WebMvcLinkBuilder> {
|
||||
|
||||
private List<UriComponentsContributor> uriComponentsContributors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Configures the {@link UriComponentsContributor} to be used when building {@link Link} instances from method
|
||||
* invocations.
|
||||
*
|
||||
* @see #linkTo(Object)
|
||||
* @param uriComponentsContributors the uriComponentsContributors to set
|
||||
*/
|
||||
public void setUriComponentsContributors(List<? extends UriComponentsContributor> uriComponentsContributors) {
|
||||
this.uriComponentsContributors = Collections.unmodifiableList(uriComponentsContributors);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller) {
|
||||
return WebMvcLinkBuilder.linkTo(controller);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller, Object... parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.LinkBuilderFactory#linkTo(java.lang.Class, java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller, Map<String, ?> parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(controller, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Class, java.lang.reflect.Method, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Class<?> controller, Method method, Object... parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(controller, method, parameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Object invocationValue) {
|
||||
|
||||
Function<String, UriComponentsBuilder> builderFactory = mapping -> UriComponentsBuilderFactory.getBuilder()
|
||||
.path(mapping);
|
||||
|
||||
return WebHandler.linkTo(invocationValue, builderFactory, WebMvcLinkBuilder::new, (builder, invocation) -> {
|
||||
|
||||
MethodParameters parameters = new MethodParameters(invocation.getMethod());
|
||||
Iterator<Object> parameterValues = Arrays.asList(invocation.getArguments()).iterator();
|
||||
|
||||
for (MethodParameter parameter : parameters.getParameters()) {
|
||||
|
||||
Object parameterValue = parameterValues.next();
|
||||
|
||||
uriComponentsContributors.stream() //
|
||||
.filter(it -> it.supportsParameter(parameter)) //
|
||||
.forEach(it -> it.enhance(builder, parameter, parameterValue));
|
||||
}
|
||||
|
||||
return builder;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.MethodLinkBuilderFactory#linkTo(java.lang.reflect.Method, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public WebMvcLinkBuilder linkTo(Method method, Object... parameters) {
|
||||
return WebMvcLinkBuilder.linkTo(method, parameters);
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.reactive;
|
||||
|
||||
import static org.springframework.hateoas.reactive.HypermediaWebFilter.*;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.core.WebHandler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Utility for building reactive {@link Link}s.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
public class ReactiveLinkBuilder extends TemplateVariableAwareLinkBuilderSupport<ReactiveLinkBuilder> {
|
||||
|
||||
private ReactiveLinkBuilder(UriComponentsBuilder builder, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(builder, variables, affordances);
|
||||
}
|
||||
|
||||
private ReactiveLinkBuilder(UriComponents components, TemplateVariables variables, List<Affordance> affordances) {
|
||||
super(components, variables, affordances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ReactiveLinkBuilder} by checking if the Reactor Context contains a {@link ServerWebExchange} and
|
||||
* using that combined with the Spring Web annotations to build a full URI. If there is no exchange, then fall back to
|
||||
* relative URIs.
|
||||
*
|
||||
* @param invocationValue
|
||||
*/
|
||||
public static Mono<ReactiveLinkBuilder> linkTo(Object invocationValue) {
|
||||
|
||||
return Mono.subscriberContext()
|
||||
.map(context -> linkTo(invocationValue, context.getOrDefault(SERVER_WEB_EXCHANGE, null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ReactiveLinkBuilder} using an explicitly defined {@link ServerWebExchange}. This is possible if
|
||||
* your WebFlux method includes the exchange and you want to pass it straight in.
|
||||
*
|
||||
* @param invocationValue
|
||||
* @param exchange
|
||||
*/
|
||||
public static ReactiveLinkBuilder linkTo(Object invocationValue, ServerWebExchange exchange) {
|
||||
|
||||
return WebHandler.linkTo(invocationValue, //
|
||||
path -> getBuilder(exchange).replacePath(path == null ? "/" : path), //
|
||||
ReactiveLinkBuilder::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the {@link ServerWebExchange}.
|
||||
*
|
||||
* @param exchange
|
||||
*/
|
||||
private static UriComponentsBuilder getBuilder(ServerWebExchange exchange) {
|
||||
|
||||
return exchange == null //
|
||||
? UriComponentsBuilder.fromPath("/") //
|
||||
: UriComponentsBuilder.fromHttpRequest(exchange.getRequest());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport#createNewInstance(org.springframework.web.util.UriComponentsBuilder, java.util.List, org.springframework.hateoas.TemplateVariables)
|
||||
*/
|
||||
@Override
|
||||
protected ReactiveLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new ReactiveLinkBuilder(builder, variables, affordances);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#getThis()
|
||||
*/
|
||||
@Override
|
||||
protected ReactiveLinkBuilder getThis() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.reactive;
|
||||
|
||||
import static org.springframework.hateoas.reactive.HypermediaWebFilter.*;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.hateoas.Affordance;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.core.DummyInvocationUtils;
|
||||
import org.springframework.hateoas.core.TemplateVariableAwareLinkBuilderSupport;
|
||||
import org.springframework.hateoas.core.WebHandler;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Utility for building reactive {@link Link}s.
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @author Oliver Drotbohm
|
||||
* @since 1.0
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WebFluxLinkBuilder} by checking if the Reactor Context contains a {@link ServerWebExchange} and
|
||||
* using that combined with the Spring Web annotations to build a full URI. If there is no exchange, then fall back to
|
||||
* relative URIs. Usually used with {@link #methodOn(Class, Object...)} to refer to a method invocation.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @see #methodOn(Class, Object...)
|
||||
*/
|
||||
public static WebFluxBuilder linkTo(Object invocation) {
|
||||
|
||||
Assert.notNull(invocation, "Invocation must not be null!");
|
||||
|
||||
return new WebFluxBuilder(linkToInternal(invocation));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WebFluxLinkBuilder} using an explicitly defined {@link ServerWebExchange}. This is possible if your
|
||||
* WebFlux method includes the exchange and you want to pass it straight in.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @param exchange must not be {@literal null}.
|
||||
*/
|
||||
public static WebFluxBuilder linkTo(Object invocation, ServerWebExchange exchange) {
|
||||
return new WebFluxBuilder(linkToInternal(invocation, exchange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for {@link DummyInvocationUtils#methodOn(Class, Object...)} to be available in case you work with static
|
||||
* imports of {@link WebFluxLinkBuilder}.
|
||||
*
|
||||
* @param controller must not be {@literal null}.
|
||||
* @param parameters parameters to extend template variables in the type level mapping.
|
||||
* @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)
|
||||
*/
|
||||
@Override
|
||||
protected WebFluxLinkBuilder createNewInstance(UriComponentsBuilder builder, List<Affordance> affordances,
|
||||
TemplateVariables variables) {
|
||||
return new WebFluxLinkBuilder(builder, variables, affordances);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.core.LinkBuilderSupport#getThis()
|
||||
*/
|
||||
@Override
|
||||
protected WebFluxLinkBuilder getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class WebFluxBuilder {
|
||||
|
||||
private final Mono<WebFluxLinkBuilder> builder;
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} for the {@link Link} with the given {@link LinkRelation}
|
||||
*
|
||||
* @param relation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink withRel(LinkRelation relation) {
|
||||
return new WebFluxLink(builder.map(it -> it.withRel(relation)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} for the {@link Link} with the given link relation.
|
||||
*
|
||||
* @param relation must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink withRel(String relation) {
|
||||
return new WebFluxLink(builder.map(it -> it.withRel(relation)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} for the {@link Link} with the {@link IanaLinkRelations#SELF}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink withSelfRel() {
|
||||
return new WebFluxLink(builder.map(WebFluxLinkBuilder::withSelfRel));
|
||||
}
|
||||
|
||||
/**
|
||||
* General callback to produce a {@link Link} from the given {@link WebFluxLinkBuilder}.
|
||||
*
|
||||
* @param finisher must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink toLink(Function<WebFluxLinkBuilder, Mono<Link>> finisher) {
|
||||
|
||||
Assert.notNull(finisher, "Finisher must not be null!");
|
||||
|
||||
return new WebFluxLink(builder.flatMap(finisher));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Intermediate representation of a {@link Link} within a reactive pipeline to easily add {@link Affordance}s from
|
||||
* method invocations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public static class WebFluxLink {
|
||||
|
||||
private final Mono<Link> link;
|
||||
|
||||
/**
|
||||
* Adds the affordance created by the given virtual method invocation.
|
||||
*
|
||||
* @param invocation must not be {@literal null}.
|
||||
* @return
|
||||
* @see WebFluxLinkBuilder#methodOn(Class, Object...)
|
||||
*/
|
||||
public WebFluxLink andAffordance(Object invocation) {
|
||||
|
||||
Assert.notNull(invocation, "Invocation must not be null!");
|
||||
|
||||
return new WebFluxLink(link.flatMap(it -> linkToInternal(invocation) //
|
||||
.flatMapIterable(WebFluxLinkBuilder::getAffordances) //
|
||||
.singleOrEmpty() //
|
||||
.map(it::andAffordance)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link WebFluxLink} with the current {@link Link} instance transformed using the given mapper.
|
||||
*
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public WebFluxLink map(Function<Link, Link> mapper) {
|
||||
|
||||
Assert.notNull(mapper, "Function must not be null!");
|
||||
|
||||
return new WebFluxLink(link.map(mapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link Mono} of {@link Link} for further handling within a reactive pipeline.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Mono<Link> toMono() {
|
||||
return link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Mono} of {@link Link} with the current one augmented by the given {@link Function}. Allows
|
||||
* immediate customization of the {@link Link} instance and immediately return to a general reactive API.
|
||||
*
|
||||
* @param finisher must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Mono<Link> toMono(Function<Link, Link> finisher) {
|
||||
|
||||
Assert.notNull(finisher, "Function must not be null!");
|
||||
|
||||
return link.map(finisher);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link UriComponentsBuilder} obtained from the {@link ServerWebExchange}.
|
||||
*
|
||||
* @param exchange
|
||||
*/
|
||||
private static UriComponentsBuilder getBuilder(ServerWebExchange exchange) {
|
||||
|
||||
return exchange == null //
|
||||
? UriComponentsBuilder.fromPath("/") //
|
||||
: UriComponentsBuilder.fromHttpRequest(exchange.getRequest());
|
||||
}
|
||||
|
||||
private static Mono<WebFluxLinkBuilder> linkToInternal(Object invocation) {
|
||||
|
||||
return Mono.subscriberContext() //
|
||||
.flatMap(context -> linkToInternal(invocation, context.getOrDefault(SERVER_WEB_EXCHANGE, null)));
|
||||
}
|
||||
|
||||
private static Mono<WebFluxLinkBuilder> linkToInternal(Object invocation, ServerWebExchange exchange) {
|
||||
|
||||
return Mono.just(WebHandler.linkTo(invocation, //
|
||||
path -> getBuilder(exchange).replacePath(path == null ? "/" : path), //
|
||||
WebFluxLinkBuilder::new));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user