#728 - Polishing.
Adapt to API changes in master branch. Fixed formatting.
This commit is contained in:
@@ -13,13 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.hateoas.core;
|
||||
package org.springframework.hateoas;
|
||||
|
||||
import static org.springframework.hateoas.TemplateVariable.VariableType.*;
|
||||
import static org.springframework.hateoas.TemplateVariables.*;
|
||||
import static org.springframework.hateoas.core.EncodingUtils.*;
|
||||
import static org.springframework.web.util.UriComponents.UriTemplateVariables.*;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -30,13 +35,21 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.hateoas.TemplateVariable;
|
||||
import org.springframework.hateoas.TemplateVariables;
|
||||
import org.springframework.hateoas.core.AnnotatedParametersParameterAccessor.BoundMethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.hateoas.core.AnnotationAttribute;
|
||||
import org.springframework.hateoas.core.AnnotationMappingDiscoverer;
|
||||
import org.springframework.hateoas.core.LastInvocationAware;
|
||||
import org.springframework.hateoas.core.MappingDiscoverer;
|
||||
import org.springframework.hateoas.core.MethodInvocation;
|
||||
import org.springframework.hateoas.core.MethodParameters;
|
||||
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
@@ -47,52 +60,45 @@ import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Utility for taking a a method invocation and extracting a {@link ControllerLinkBuilder}.
|
||||
*
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
public class WebHandler {
|
||||
|
||||
private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class);
|
||||
private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor(
|
||||
new AnnotationAttribute(PathVariable.class));
|
||||
new AnnotationAttribute(PathVariable.class));
|
||||
private static final AnnotatedParametersParameterAccessor REQUEST_PARAM_ACCESSOR = new RequestParamParameterAccessor();
|
||||
|
||||
public static ControllerLinkBuilder linkTo(Object invocationValue,
|
||||
Function<String, UriComponentsBuilder> mappingToUriComponentsBuilder,
|
||||
BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
|
||||
Function<String, UriComponentsBuilder> mappingToUriComponentsBuilder,
|
||||
BiFunction<UriComponentsBuilder, MethodInvocation, UriComponentsBuilder> additionalUriHandler) {
|
||||
|
||||
Assert.isInstanceOf(LastInvocationAware.class, invocationValue);
|
||||
|
||||
|
||||
LastInvocationAware invocations = (LastInvocationAware) invocationValue;
|
||||
MethodInvocation invocation = invocations.getLastInvocation();
|
||||
|
||||
String mapping = DISCOVERER.getMapping(invocation.getTargetType(), invocation.getMethod());
|
||||
|
||||
UriComponentsBuilder builder = mappingToUriComponentsBuilder.apply(mapping);
|
||||
|
||||
UriTemplate template;
|
||||
if (mapping == null) {
|
||||
template = new UriTemplate("/");
|
||||
} else {
|
||||
template = new UriTemplate(mapping);
|
||||
}
|
||||
|
||||
UriTemplate template = new UriTemplate(mapping == null ? "/" : mapping);
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
|
||||
Iterator<String> names = template.getVariableNames().iterator();
|
||||
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
|
||||
|
||||
|
||||
while (classMappingParameters.hasNext()) {
|
||||
values.put(names.next(), encodePath(classMappingParameters.next()));
|
||||
}
|
||||
|
||||
for (BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) {
|
||||
for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : PATH_VARIABLE_ACCESSOR.getBoundParameters(invocation)) {
|
||||
values.put(parameter.getVariableName(), encodePath(parameter.asString()));
|
||||
}
|
||||
|
||||
List<String> optionalEmptyParameters = new ArrayList<>();
|
||||
|
||||
for (BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) {
|
||||
for (AnnotatedParametersParameterAccessor.BoundMethodParameter parameter : REQUEST_PARAM_ACCESSOR.getBoundParameters(invocation)) {
|
||||
|
||||
bindRequestParameters(builder, parameter);
|
||||
|
||||
@@ -112,12 +118,9 @@ public class WebHandler {
|
||||
}
|
||||
}
|
||||
|
||||
UriComponents components;
|
||||
if (additionalUriHandler == null) {
|
||||
components = builder.buildAndExpand(values);
|
||||
} else {
|
||||
components = additionalUriHandler.apply(builder, invocation).buildAndExpand(values);
|
||||
}
|
||||
UriComponents components = additionalUriHandler == null //
|
||||
? builder.buildAndExpand(values)
|
||||
: additionalUriHandler.apply(builder, invocation).buildAndExpand(values);
|
||||
|
||||
TemplateVariables variables = NONE;
|
||||
|
||||
@@ -125,22 +128,22 @@ public class WebHandler {
|
||||
|
||||
boolean previousRequestParameter = components.getQueryParams().isEmpty() && variables.equals(NONE);
|
||||
TemplateVariable variable = new TemplateVariable(parameter,
|
||||
previousRequestParameter ? REQUEST_PARAM : REQUEST_PARAM_CONTINUED);
|
||||
previousRequestParameter ? REQUEST_PARAM : REQUEST_PARAM_CONTINUED);
|
||||
variables = variables.concat(variable);
|
||||
}
|
||||
|
||||
return new ControllerLinkBuilder(components, variables, invocation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Populates the given {@link UriComponentsBuilder} with request parameters found in the given
|
||||
* {@link BoundMethodParameter}.
|
||||
* {@link AnnotatedParametersParameterAccessor.BoundMethodParameter}.
|
||||
*
|
||||
* @param builder must not be {@literal null}.
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void bindRequestParameters(UriComponentsBuilder builder, BoundMethodParameter parameter) {
|
||||
private static void bindRequestParameters(UriComponentsBuilder builder, AnnotatedParametersParameterAccessor.BoundMethodParameter parameter) {
|
||||
|
||||
Object value = parameter.getValue();
|
||||
String key = parameter.getVariableName();
|
||||
@@ -198,7 +201,7 @@ public class WebHandler {
|
||||
*/
|
||||
@Override
|
||||
protected BoundMethodParameter createParameter(final MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
AnnotationAttribute attribute) {
|
||||
|
||||
return new BoundMethodParameter(parameter, value, attribute) {
|
||||
|
||||
@@ -243,4 +246,162 @@ public class WebHandler {
|
||||
return annotation.defaultValue().equals(ValueConstants.DEFAULT_NONE) ? SKIP_VALUE : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to allow accessing {@link MethodInvocation} parameters with the configured {@link AnnotationAttribute}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class AnnotatedParametersParameterAccessor {
|
||||
|
||||
private static final Map<Method, MethodParameters> METHOD_PARAMETERS_CACHE = new ConcurrentReferenceHashMap<>(
|
||||
16, ConcurrentReferenceHashMap.ReferenceType.WEAK);
|
||||
|
||||
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 = getOrCreateMethodParametersFor(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected BoundMethodParameter createParameter(MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
return new BoundMethodParameter(parameter, value, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodParameters} for the given {@link Method}.
|
||||
*
|
||||
* @param method
|
||||
* @return
|
||||
*/
|
||||
private static MethodParameters getOrCreateMethodParametersFor(Method method) {
|
||||
return METHOD_PARAMETERS_CACHE.computeIfAbsent(method, MethodParameters::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Object value, AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
|
||||
this.parameter = parameter;
|
||||
this.value = value;
|
||||
this.attribute = attribute;
|
||||
this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, parameter.isOptional() ? 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
|
||||
*/
|
||||
public String getVariableName() {
|
||||
|
||||
if (attribute == null) {
|
||||
return parameter.getParameterName();
|
||||
}
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String annotationAttributeValue = attribute.getValueFrom(annotation);
|
||||
|
||||
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw value bound to the {@link MethodParameter}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound value converted into a {@link String} based on default conversion service setup.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
|
||||
return value == null //
|
||||
? null //
|
||||
: (String) CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given parameter is a required one. Defaults to {@literal true}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,15 +95,5 @@ public @interface EnableHypermediaSupport {
|
||||
UBER;
|
||||
|
||||
private static Set<HypermediaType> HAL_BASED_MEDIATYPES = EnumSet.of(HAL, HAL_FORMS);
|
||||
|
||||
/**
|
||||
* Is this {@literal HAL} or one of its derivatives?
|
||||
*
|
||||
* @param hypermediaType
|
||||
* @return
|
||||
*/
|
||||
public static boolean isHalBasedMediaType(HypermediaType hypermediaType) {
|
||||
return HAL_BASED_MEDIATYPES.contains(hypermediaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.hateoas.config;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule;
|
||||
import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule.CollectionJsonHandlerInstantiator;
|
||||
import org.springframework.hateoas.hal.CurieProvider;
|
||||
import org.springframework.hateoas.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.hal.Jackson2HalModule;
|
||||
@@ -27,14 +26,13 @@ import org.springframework.hateoas.hal.forms.HalFormsConfiguration;
|
||||
import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule;
|
||||
import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule.HalFormsHandlerInstantiator;
|
||||
import org.springframework.hateoas.uber.Jackson2UberModule;
|
||||
import org.springframework.hateoas.uber.Jackson2UberModule.UberHandlerInstantiator;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Utility class for create {@link ObjectMapper} instances of all hypermedia types.
|
||||
*
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -42,7 +40,7 @@ public final class HypermediaObjectMapperCreator {
|
||||
|
||||
/**
|
||||
* Create a {@link org.springframework.hateoas.MediaTypes#HAL_JSON}-based {@link ObjectMapper}.
|
||||
*
|
||||
*
|
||||
* @param objectMapper
|
||||
* @param curieProvider
|
||||
* @param relProvider
|
||||
@@ -50,18 +48,15 @@ public final class HypermediaObjectMapperCreator {
|
||||
* @param halConfiguration
|
||||
* @return
|
||||
*/
|
||||
public static ObjectMapper createHalObjectMapper(ObjectMapper objectMapper,
|
||||
CurieProvider curieProvider,
|
||||
RelProvider relProvider,
|
||||
MessageSourceAccessor linkRelationMessageSource,
|
||||
HalConfiguration halConfiguration) {
|
||||
public static ObjectMapper createHalObjectMapper(ObjectMapper objectMapper, CurieProvider curieProvider,
|
||||
RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource, HalConfiguration halConfiguration) {
|
||||
|
||||
ObjectMapper mapper = objectMapper.copy();
|
||||
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.registerModule(new Jackson2HalModule());
|
||||
mapper.setHandlerInstantiator(new HalHandlerInstantiator(relProvider, curieProvider,
|
||||
linkRelationMessageSource, halConfiguration));
|
||||
mapper.setHandlerInstantiator(
|
||||
new HalHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource, halConfiguration));
|
||||
|
||||
return mapper;
|
||||
}
|
||||
@@ -76,19 +71,16 @@ public final class HypermediaObjectMapperCreator {
|
||||
* @param halFormsConfiguration
|
||||
* @return properly configured objectMapper
|
||||
*/
|
||||
public static ObjectMapper createHalFormsObjectMapper(ObjectMapper objectMapper,
|
||||
CurieProvider curieProvider,
|
||||
RelProvider relProvider,
|
||||
MessageSourceAccessor linkRelationMessageSource,
|
||||
HalFormsConfiguration halFormsConfiguration) {
|
||||
public static ObjectMapper createHalFormsObjectMapper(ObjectMapper objectMapper, CurieProvider curieProvider,
|
||||
RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource,
|
||||
HalFormsConfiguration halFormsConfiguration) {
|
||||
|
||||
ObjectMapper mapper = objectMapper.copy();
|
||||
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.registerModule(new Jackson2HalFormsModule());
|
||||
mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(
|
||||
relProvider, curieProvider, linkRelationMessageSource, true,
|
||||
halFormsConfiguration));
|
||||
mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource,
|
||||
true, halFormsConfiguration));
|
||||
|
||||
return mapper;
|
||||
}
|
||||
@@ -100,14 +92,12 @@ public final class HypermediaObjectMapperCreator {
|
||||
* @param linkRelationMessageSource
|
||||
* @return properly configured objectMapper
|
||||
*/
|
||||
public static ObjectMapper createCollectionJsonObjectMapper(ObjectMapper objectMapper,
|
||||
MessageSourceAccessor linkRelationMessageSource) {
|
||||
public static ObjectMapper createCollectionJsonObjectMapper(ObjectMapper objectMapper) {
|
||||
|
||||
ObjectMapper mapper = objectMapper.copy();
|
||||
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.registerModule(new Jackson2CollectionJsonModule());
|
||||
mapper.setHandlerInstantiator(new CollectionJsonHandlerInstantiator(linkRelationMessageSource));
|
||||
|
||||
return mapper;
|
||||
}
|
||||
@@ -124,7 +114,6 @@ public final class HypermediaObjectMapperCreator {
|
||||
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
mapper.registerModule(new Jackson2UberModule());
|
||||
mapper.setHandlerInstantiator(new UberHandlerInstantiator());
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
@@ -79,11 +79,11 @@ public class HypermediaWebMvcConfigurer implements WebMvcConfigurer, BeanFactory
|
||||
@Override
|
||||
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
|
||||
if (converters.stream()
|
||||
.filter(MappingJackson2HttpMessageConverter.class::isInstance)
|
||||
.map(AbstractJackson2HttpMessageConverter.class::cast)
|
||||
.map(AbstractJackson2HttpMessageConverter::getObjectMapper)
|
||||
.anyMatch(Jackson2HalModule::isAlreadyRegisteredIn)) {
|
||||
if (converters.stream().filter(MappingJackson2HttpMessageConverter.class::isInstance)
|
||||
.filter(AbstractJackson2HttpMessageConverter.class::isInstance)
|
||||
.map(AbstractJackson2HttpMessageConverter.class::cast)
|
||||
.map(AbstractJackson2HttpMessageConverter::getObjectMapper)
|
||||
.anyMatch(Jackson2HalModule::isAlreadyRegisteredIn)) {
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -91,36 +91,32 @@ public class HypermediaWebMvcConfigurer implements WebMvcConfigurer, BeanFactory
|
||||
MessageSourceAccessor linkRelationMessageSource = this.beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
|
||||
MessageSourceAccessor.class);
|
||||
|
||||
if (this.hypermediaTypes.stream().anyMatch(HypermediaType::isHalBasedMediaType)) {
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
|
||||
|
||||
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(
|
||||
ResourceSupport.class, Arrays.asList(HAL_JSON, HAL_JSON_UTF8),
|
||||
createHalObjectMapper(this.mapper, this.curieProvider, this.relProvider, linkRelationMessageSource,
|
||||
this.halConfiguration)));
|
||||
}
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
|
||||
|
||||
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(
|
||||
ResourceSupport.class, Arrays.asList(HAL_FORMS_JSON),
|
||||
createHalFormsObjectMapper(this.mapper, this.curieProvider, this.relProvider, linkRelationMessageSource,
|
||||
this.halFormsConfiguration)));
|
||||
}
|
||||
converters.add(0,
|
||||
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class,
|
||||
Arrays.asList(HAL_JSON, HAL_JSON_UTF8), createHalObjectMapper(this.mapper, this.curieProvider,
|
||||
this.relProvider, linkRelationMessageSource, this.halConfiguration)));
|
||||
}
|
||||
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
|
||||
|
||||
converters.add(0,
|
||||
new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class,
|
||||
Collections.singletonList(HAL_FORMS_JSON), createHalFormsObjectMapper(this.mapper, this.curieProvider,
|
||||
this.relProvider, linkRelationMessageSource, this.halFormsConfiguration)));
|
||||
}
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) {
|
||||
|
||||
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(
|
||||
ResourceSupport.class, Arrays.asList(COLLECTION_JSON),
|
||||
createCollectionJsonObjectMapper(this.mapper, linkRelationMessageSource)));
|
||||
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class,
|
||||
Arrays.asList(COLLECTION_JSON), createCollectionJsonObjectMapper(this.mapper)));
|
||||
}
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.UBER)) {
|
||||
|
||||
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(
|
||||
ResourceSupport.class, Arrays.asList(UBER_JSON), createUberObjectMapper(this.mapper)));
|
||||
converters.add(0, new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class,
|
||||
Collections.singletonList(UBER_JSON), createUberObjectMapper(this.mapper)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
|
||||
@@ -44,7 +45,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
/**
|
||||
* {@link WebFluxConfigurer} to register hypermedia-aware {@link org.springframework.core.codec.Encoder}s and
|
||||
* {@link org.springframework.core.codec.Decoder}s that will render hypermedia for WebFlux controllers.
|
||||
*
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -62,7 +63,7 @@ public class HypermediaWebFluxConfigurer implements WebFluxConfigurer, BeanFacto
|
||||
private final Collection<HypermediaType> hypermediaTypes;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
@@ -70,8 +71,8 @@ public class HypermediaWebFluxConfigurer implements WebFluxConfigurer, BeanFacto
|
||||
|
||||
/**
|
||||
* Configure custom HTTP message readers and writers or override built-in ones.
|
||||
* <p>The configured readers and writers will be used for both annotated
|
||||
* controllers and functional endpoints.
|
||||
* <p>
|
||||
* The configured readers and writers will be used for both annotated controllers and functional endpoints.
|
||||
*
|
||||
* @param configurer the configurer to use
|
||||
*/
|
||||
@@ -79,67 +80,59 @@ public class HypermediaWebFluxConfigurer implements WebFluxConfigurer, BeanFacto
|
||||
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
|
||||
|
||||
if (configurer.getReaders().stream()
|
||||
.flatMap(httpMessageReader -> httpMessageReader.getReadableMediaTypes().stream())
|
||||
.anyMatch(mediaType -> mediaType == MediaTypes.HAL_JSON
|
||||
|| mediaType == MediaTypes.HAL_JSON_UTF8
|
||||
|| mediaType == MediaTypes.HAL_FORMS_JSON
|
||||
|| mediaType == MediaTypes.COLLECTION_JSON
|
||||
|| mediaType == MediaTypes.UBER_JSON)) {
|
||||
.flatMap(httpMessageReader -> httpMessageReader.getReadableMediaTypes().stream())
|
||||
.anyMatch(mediaType -> mediaType == MediaTypes.HAL_JSON || mediaType == MediaTypes.HAL_JSON_UTF8
|
||||
|| mediaType == MediaTypes.HAL_FORMS_JSON || mediaType == MediaTypes.COLLECTION_JSON
|
||||
|| mediaType == MediaTypes.UBER_JSON)) {
|
||||
|
||||
// Already configured for hypermedia!
|
||||
return;
|
||||
}
|
||||
|
||||
MessageSourceAccessor linkRelationMessageSource = this.beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
|
||||
MessageSourceAccessor.class);
|
||||
MessageSourceAccessor.class);
|
||||
|
||||
CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
|
||||
|
||||
if (this.hypermediaTypes.stream().anyMatch(HypermediaType::isHalBasedMediaType)) {
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
|
||||
ObjectMapper halObjectMapper = createHalObjectMapper(this.mapper, this.curieProvider, this.relProvider,
|
||||
linkRelationMessageSource, this.halConfiguration);
|
||||
|
||||
ObjectMapper halObjectMapper = createHalObjectMapper(this.mapper, this.curieProvider, this.relProvider,
|
||||
linkRelationMessageSource, this.halConfiguration);
|
||||
customCodecs.encoder(
|
||||
new Jackson2JsonEncoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
customCodecs.decoder(
|
||||
new Jackson2JsonDecoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
}
|
||||
|
||||
customCodecs.encoder(
|
||||
new Jackson2JsonEncoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
customCodecs.decoder(
|
||||
new Jackson2JsonDecoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
}
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
|
||||
ObjectMapper halFormsObjectMapper = createHalFormsObjectMapper(this.mapper, this.curieProvider,
|
||||
this.relProvider, linkRelationMessageSource, this.halFormsConfiguration);
|
||||
|
||||
ObjectMapper halFormsObjectMapper = createHalFormsObjectMapper(this.mapper, this.curieProvider,
|
||||
this.relProvider, linkRelationMessageSource, this.halFormsConfiguration);
|
||||
|
||||
customCodecs.encoder(
|
||||
new Jackson2JsonEncoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
customCodecs.decoder(
|
||||
new Jackson2JsonDecoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
}
|
||||
customCodecs.encoder(
|
||||
new Jackson2JsonEncoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
customCodecs.decoder(
|
||||
new Jackson2JsonDecoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
}
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) {
|
||||
|
||||
ObjectMapper collectionJsonObjectMapper = createCollectionJsonObjectMapper(this.mapper, linkRelationMessageSource);
|
||||
ObjectMapper collectionJsonObjectMapper = createCollectionJsonObjectMapper(this.mapper);
|
||||
|
||||
customCodecs.encoder(
|
||||
new Jackson2JsonEncoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
|
||||
customCodecs.decoder(
|
||||
new Jackson2JsonDecoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
|
||||
customCodecs.encoder(new Jackson2JsonEncoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
|
||||
customCodecs.decoder(new Jackson2JsonDecoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
|
||||
}
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.UBER)) {
|
||||
|
||||
ObjectMapper uberObjectMapper = createUberObjectMapper(this.mapper);
|
||||
|
||||
customCodecs.encoder(
|
||||
new Jackson2JsonEncoder(uberObjectMapper, MediaTypes.UBER_JSON));
|
||||
customCodecs.decoder(
|
||||
new Jackson2JsonDecoder(uberObjectMapper, MediaTypes.UBER_JSON));
|
||||
customCodecs.encoder(new Jackson2JsonEncoder(uberObjectMapper, MediaTypes.UBER_JSON));
|
||||
customCodecs.decoder(new Jackson2JsonDecoder(uberObjectMapper, MediaTypes.UBER_JSON));
|
||||
}
|
||||
|
||||
customCodecs.encoder(CharSequenceEncoder.allMimeTypes());
|
||||
customCodecs.decoder(StringDecoder.allMimeTypes());
|
||||
|
||||
configurer.registerDefaults(false);
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.codec.StringDecoder;
|
||||
@@ -46,7 +47,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Assembles {@link ExchangeStrategies} needed to wire a {@link WebClient} with hypermedia support.
|
||||
*
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -64,7 +65,7 @@ public class WebClientConfigurer implements BeanFactoryAware {
|
||||
private final Collection<HypermediaType> hypermediaTypes;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
@@ -72,40 +73,38 @@ public class WebClientConfigurer implements BeanFactoryAware {
|
||||
|
||||
/**
|
||||
* Return a set of {@link ExchangeStrategies} driven by registered {@link HypermediaType}s.
|
||||
*
|
||||
* @return a collection of {@link Encoder}s and {@link Decoder} assembled into a {@link ExchangeStrategies}.
|
||||
*/
|
||||
public ExchangeStrategies hypermediaExchangeStrategies() {
|
||||
|
||||
MessageSourceAccessor linkRelationMessageSource = this.beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
|
||||
MessageSourceAccessor.class);
|
||||
MessageSourceAccessor.class);
|
||||
|
||||
List<Encoder<?>> encoders = new ArrayList<>();
|
||||
List<Decoder<?>> decoders = new ArrayList<>();
|
||||
|
||||
if (this.hypermediaTypes.stream().anyMatch(HypermediaType::isHalBasedMediaType)) {
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL)) {
|
||||
ObjectMapper halObjectMapper = createHalObjectMapper(this.mapper, this.curieProvider, this.relProvider,
|
||||
linkRelationMessageSource, this.halConfiguration);
|
||||
|
||||
ObjectMapper halObjectMapper = createHalObjectMapper(this.mapper, this.curieProvider, this.relProvider,
|
||||
linkRelationMessageSource, this.halConfiguration);
|
||||
encoders.add(new Jackson2JsonEncoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
decoders.add(new Jackson2JsonDecoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
}
|
||||
|
||||
encoders.add(new Jackson2JsonEncoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
decoders.add(new Jackson2JsonDecoder(halObjectMapper, MediaTypes.HAL_JSON, MediaTypes.HAL_JSON_UTF8));
|
||||
}
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
|
||||
ObjectMapper halFormsObjectMapper = createHalFormsObjectMapper(this.mapper, this.curieProvider,
|
||||
this.relProvider, linkRelationMessageSource, this.halFormsConfiguration);
|
||||
|
||||
ObjectMapper halFormsObjectMapper = createHalFormsObjectMapper(this.mapper, this.curieProvider,
|
||||
this.relProvider, linkRelationMessageSource, this.halFormsConfiguration);
|
||||
|
||||
encoders.add(new Jackson2JsonEncoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
decoders.add(new Jackson2JsonDecoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
}
|
||||
encoders.add(new Jackson2JsonEncoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
decoders.add(new Jackson2JsonDecoder(halFormsObjectMapper, MediaTypes.HAL_FORMS_JSON));
|
||||
}
|
||||
|
||||
if (this.hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) {
|
||||
|
||||
ObjectMapper collectionJsonObjectMapper = createCollectionJsonObjectMapper(this.mapper, linkRelationMessageSource);
|
||||
ObjectMapper collectionJsonObjectMapper = createCollectionJsonObjectMapper(this.mapper);
|
||||
|
||||
encoders.add(new Jackson2JsonEncoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
|
||||
decoders.add(new Jackson2JsonDecoder(collectionJsonObjectMapper, MediaTypes.COLLECTION_JSON));
|
||||
@@ -119,18 +118,16 @@ public class WebClientConfigurer implements BeanFactoryAware {
|
||||
decoders.add(new Jackson2JsonDecoder(uberObjectMapper, MediaTypes.UBER_JSON));
|
||||
}
|
||||
|
||||
// Include ability to decode into a plain string
|
||||
encoders.add(CharSequenceEncoder.allMimeTypes());
|
||||
decoders.add(StringDecoder.allMimeTypes());
|
||||
|
||||
return ExchangeStrategies.builder()
|
||||
.codecs(clientCodecConfigurer -> {
|
||||
return ExchangeStrategies.builder().codecs(clientCodecConfigurer -> {
|
||||
|
||||
encoders.forEach(encoder -> clientCodecConfigurer.customCodecs().encoder(encoder));
|
||||
decoders.forEach(decoder -> clientCodecConfigurer.customCodecs().decoder(decoder));
|
||||
encoders.forEach(encoder -> clientCodecConfigurer.customCodecs().encoder(encoder));
|
||||
decoders.forEach(decoder -> clientCodecConfigurer.customCodecs().decoder(decoder));
|
||||
|
||||
clientCodecConfigurer.registerDefaults(false);
|
||||
})
|
||||
.build();
|
||||
clientCodecConfigurer.registerDefaults(false);
|
||||
}).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,9 +138,6 @@ public class WebClientConfigurer implements BeanFactoryAware {
|
||||
*/
|
||||
public WebClient registerHypermediaTypes(WebClient webClient) {
|
||||
|
||||
return webClient.mutate()
|
||||
.exchangeStrategies(hypermediaExchangeStrategies())
|
||||
.build();
|
||||
return webClient.mutate().exchangeStrategies(hypermediaExchangeStrategies()).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
|
||||
/**
|
||||
* Value object to allow accessing {@link MethodInvocation} parameters with the configured {@link AnnotationAttribute}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class AnnotatedParametersParameterAccessor {
|
||||
|
||||
private static final Map<Method, MethodParameters> METHOD_PARAMETERS_CACHE = new ConcurrentReferenceHashMap<>(
|
||||
16, ReferenceType.WEAK);
|
||||
|
||||
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 = getOrCreateMethodParametersFor(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected BoundMethodParameter createParameter(MethodParameter parameter, Object value,
|
||||
AnnotationAttribute attribute) {
|
||||
return new BoundMethodParameter(parameter, value, attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected Object verifyParameterValue(MethodParameter parameter, Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MethodParameters} for the given {@link Method}.
|
||||
*
|
||||
* @param method
|
||||
* @return
|
||||
*/
|
||||
private static MethodParameters getOrCreateMethodParametersFor(Method method) {
|
||||
return METHOD_PARAMETERS_CACHE.computeIfAbsent(method, MethodParameters::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, Object value, AnnotationAttribute attribute) {
|
||||
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
|
||||
this.parameter = parameter;
|
||||
this.value = value;
|
||||
this.attribute = attribute;
|
||||
this.parameterTypeDescriptor = TypeDescriptor.nested(parameter, parameter.isOptional() ? 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
|
||||
*/
|
||||
public String getVariableName() {
|
||||
|
||||
if (attribute == null) {
|
||||
return parameter.getParameterName();
|
||||
}
|
||||
|
||||
Annotation annotation = parameter.getParameterAnnotation(attribute.getAnnotationType());
|
||||
String annotationAttributeValue = attribute.getValueFrom(annotation);
|
||||
|
||||
return StringUtils.hasText(annotationAttributeValue) ? annotationAttributeValue : parameter.getParameterName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw value bound to the {@link MethodParameter}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bound value converted into a {@link String} based on default conversion service setup.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String asString() {
|
||||
|
||||
return value == null //
|
||||
? null //
|
||||
: (String) CONVERSION_SERVICE.convert(value, parameterTypeDescriptor, STRING_DESCRIPTOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given parameter is a required one. Defaults to {@literal true}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ 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.hateoas.WebHandler;
|
||||
|
||||
/**
|
||||
* Factory for {@link LinkBuilderSupport} instances based on the request mapping annotated on the given controller.
|
||||
|
||||
@@ -18,9 +18,10 @@ package org.springframework.hateoas.reactive;
|
||||
import static org.springframework.hateoas.reactive.HypermediaWebFilter.*;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.WebHandler;
|
||||
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
|
||||
import org.springframework.hateoas.core.WebHandler;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
@@ -37,19 +38,18 @@ public class ReactiveLinkBuilder {
|
||||
private ReactiveLinkBuilder(ControllerLinkBuilder controllerLinkBuilder) {
|
||||
this.controllerLinkBuilder = controllerLinkBuilder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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)));
|
||||
.map(context -> linkTo(invocationValue, context.getOrDefault(SERVER_WEB_EXCHANGE, null)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,19 +61,9 @@ public class ReactiveLinkBuilder {
|
||||
*/
|
||||
public static ReactiveLinkBuilder linkTo(Object invocationValue, ServerWebExchange exchange) {
|
||||
|
||||
ControllerLinkBuilder controllerLinkBuilder = WebHandler.linkTo(invocationValue, path -> {
|
||||
ControllerLinkBuilder controllerLinkBuilder = WebHandler //
|
||||
.linkTo(invocationValue, path -> getBuilder(exchange).replacePath(path == null ? "/" : path), null);
|
||||
|
||||
UriComponentsBuilder builder = getBuilder(exchange);
|
||||
|
||||
if (path == null) {
|
||||
builder.replacePath("/");
|
||||
} else {
|
||||
builder.replacePath(path);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}, null);
|
||||
|
||||
return new ReactiveLinkBuilder(controllerLinkBuilder);
|
||||
}
|
||||
|
||||
@@ -100,10 +90,8 @@ public class ReactiveLinkBuilder {
|
||||
*/
|
||||
private static UriComponentsBuilder getBuilder(ServerWebExchange exchange) {
|
||||
|
||||
if (exchange == null) {
|
||||
return UriComponentsBuilder.fromPath("/");
|
||||
}
|
||||
|
||||
return UriComponentsBuilder.fromHttpRequest(exchange.getRequest());
|
||||
return exchange == null //
|
||||
? UriComponentsBuilder.fromPath("/") //
|
||||
: UriComponentsBuilder.fromHttpRequest(exchange.getRequest());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.hateoas.reactive;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceAssembler;
|
||||
import org.springframework.hateoas.Resources;
|
||||
@@ -25,7 +26,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
/**
|
||||
* Reactive variant of {@link ResourceAssembler} combined with {@link SimpleResourceAssembler}.
|
||||
*
|
||||
*
|
||||
* @author Greg Turnquist
|
||||
* @since 1.0
|
||||
*/
|
||||
@@ -60,14 +61,14 @@ public interface SimpleReactiveResourceAssembler<T> extends ReactiveResourceAsse
|
||||
*/
|
||||
default Mono<Resources<Resource<T>>> toResources(Flux<? extends T> entities, ServerWebExchange exchange) {
|
||||
|
||||
return entities
|
||||
.flatMap(entity -> toResource(entity, exchange))
|
||||
.collectList()
|
||||
.map(listOfResources -> {
|
||||
Resources<Resource<T>> resources = new Resources<>(listOfResources);
|
||||
addLinks(resources, exchange);
|
||||
return resources;
|
||||
});
|
||||
return entities //
|
||||
.flatMap(entity -> toResource(entity, exchange)) //
|
||||
.collectList() //
|
||||
.map(listOfResources -> {
|
||||
Resources<Resource<T>> resources = new Resources<>(listOfResources);
|
||||
addLinks(resources, exchange);
|
||||
return resources;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,5 +77,4 @@ public interface SimpleReactiveResourceAssembler<T> extends ReactiveResourceAsse
|
||||
* @param resources
|
||||
*/
|
||||
void addLinks(Resources<Resource<T>> resources, ServerWebExchange exchange);
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
@@ -73,6 +75,13 @@ public class PropertyUtils {
|
||||
|
||||
public static List<String> findPropertyNames(ResolvableType resolvableType) {
|
||||
|
||||
if (WebStack.WEBFLUX.isAvailable()) {
|
||||
if (Mono.class.equals(resolvableType.getRawClass()) || Flux.class.equals(resolvableType.getRawClass())) {
|
||||
ResolvableType generic = resolvableType.getGeneric(0);
|
||||
return findPropertyNames(generic);
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvableType.getRawClass().equals(Resource.class)) {
|
||||
return findPropertyNames(resolvableType.resolveGeneric(0));
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user