Polishing.
Imports, Javadoc, ticket references in test cases. Removed the need for MethodParameterAware… flavors of the ResourceAssemblers by keeping the MethodParameter reference in the original assemblers in the first place. Extracted common Pageable MethodParameter lookup code into PageableMethodParameterUtils. Related ticket: #1307.
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.web;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
/**
|
||||
* Custom {@link PagedResourcesAssembler} that is aware of the {@link MethodParameter} it shall create links for.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 1.7
|
||||
*/
|
||||
class MethodParameterAwarePagedResourcesAssembler<T> extends PagedResourcesAssembler<T> {
|
||||
|
||||
private final MethodParameter parameter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MethodParameterAwarePagedResourcesAssembler} using the given {@link MethodParameter},
|
||||
* {@link HateoasPageableHandlerMethodArgumentResolver} and base URI.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param resolver can be {@literal null}.
|
||||
* @param baseUri can be {@literal null}.
|
||||
*/
|
||||
public MethodParameterAwarePagedResourcesAssembler(MethodParameter parameter,
|
||||
@Nullable HateoasPageableHandlerMethodArgumentResolver resolver, @Nullable UriComponents baseUri) {
|
||||
|
||||
super(resolver, baseUri);
|
||||
|
||||
Assert.notNull(parameter, "Method parameter must not be null");
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected MethodParameter getMethodParameter() {
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.web;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
|
||||
/**
|
||||
* Custom {@link SlicedResourcesAssembler} that is aware of the {@link MethodParameter} it shall create links for.
|
||||
*
|
||||
* @author Michael Schout
|
||||
*/
|
||||
public class MethodParameterAwareSlicedResourcesAssembler<T> extends SlicedResourcesAssembler<T> {
|
||||
private final MethodParameter parameter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MethodParameterAwareSlicedResourcesAssembler} using the given
|
||||
* {@link MethodParameter}, {@link HateoasPageableHandlerMethodArgumentResolver} and base
|
||||
* URI.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param resolver can be {@literal null}.
|
||||
* @param baseUri can be {@literal null}.
|
||||
*/
|
||||
public MethodParameterAwareSlicedResourcesAssembler(MethodParameter parameter,
|
||||
@Nullable HateoasPageableHandlerMethodArgumentResolver resolver, @Nullable UriComponents baseUri) {
|
||||
|
||||
super(resolver, baseUri);
|
||||
|
||||
Assert.notNull(parameter, "Method parameter must not be null");
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected MethodParameter getMethodParameter() {
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.web;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.hateoas.server.core.MethodParameters;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Utility methods to obtain a {@link MethodParameter} of type {@link Pageable} with the same {@link Qualifier}.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.1
|
||||
* @soundtrack The Intersphere - Wanderer (https://www.youtube.com/watch?v=Sp_VyFBbDPA)
|
||||
*/
|
||||
class PageableMethodParameterUtils {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PageableMethodParameterUtils.class);
|
||||
|
||||
private static final String SUPERFLOUS_QUALIFIER = "Found qualified %s parameter, but a unique unqualified %s parameter; Using that one, but you might want to check your controller method configuration";
|
||||
private static final String PARAMETER_AMBIGUITY = "Discovered multiple parameters of type Pageable but no qualifier annotations to disambiguate";
|
||||
|
||||
/**
|
||||
* Returns finds the {@link MethodParameter} for a {@link Pageable} instance matching the given
|
||||
* {@link MethodParameter} requesting a {@link PagedResourcesAssembler}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
static MethodParameter findMatchingPageableParameter(MethodParameter parameter) {
|
||||
|
||||
Method method = parameter.getMethod();
|
||||
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException(String.format("Could not obtain method from parameter %s", parameter));
|
||||
}
|
||||
|
||||
MethodParameters parameters = MethodParameters.of(method);
|
||||
List<MethodParameter> pageableParameters = parameters.getParametersOfType(Pageable.class);
|
||||
Qualifier assemblerQualifier = parameter.getParameterAnnotation(Qualifier.class);
|
||||
|
||||
if (pageableParameters.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pageableParameters.size() == 1) {
|
||||
|
||||
MethodParameter pageableParameter = pageableParameters.get(0);
|
||||
MethodParameter matchingParameter = returnIfQualifiersMatch(pageableParameter, assemblerQualifier);
|
||||
|
||||
if (matchingParameter == null) {
|
||||
logger.info(LogMessage.format(SUPERFLOUS_QUALIFIER, PagedResourcesAssembler.class.getSimpleName(),
|
||||
Pageable.class.getName()));
|
||||
}
|
||||
|
||||
return pageableParameter;
|
||||
}
|
||||
|
||||
if (assemblerQualifier == null) {
|
||||
throw new IllegalStateException(PARAMETER_AMBIGUITY);
|
||||
}
|
||||
|
||||
for (MethodParameter pageableParameter : pageableParameters) {
|
||||
|
||||
MethodParameter matchingParameter = returnIfQualifiersMatch(pageableParameter, assemblerQualifier);
|
||||
|
||||
if (matchingParameter != null) {
|
||||
return matchingParameter;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(PARAMETER_AMBIGUITY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MethodParameter returnIfQualifiersMatch(MethodParameter pageableParameter,
|
||||
@Nullable Qualifier assemblerQualifier) {
|
||||
|
||||
if (assemblerQualifier == null) {
|
||||
return pageableParameter;
|
||||
}
|
||||
|
||||
Qualifier pageableParameterQualifier = pageableParameter.getParameterAnnotation(Qualifier.class);
|
||||
|
||||
return pageableParameterQualifier != null
|
||||
? pageableParameterQualifier.value().equals(assemblerQualifier.value()) ? pageableParameter : null
|
||||
: null;
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ public class PagedResourcesAssembler<T> implements RepresentationModelAssembler<
|
||||
private final EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
|
||||
|
||||
private boolean forceFirstAndLastRels = false;
|
||||
private @Nullable MethodParameter parameter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PagedResourcesAssembler} using the given {@link PageableHandlerMethodArgumentResolver} and
|
||||
@@ -69,9 +70,15 @@ public class PagedResourcesAssembler<T> implements RepresentationModelAssembler<
|
||||
*/
|
||||
public PagedResourcesAssembler(@Nullable HateoasPageableHandlerMethodArgumentResolver resolver,
|
||||
@Nullable UriComponents baseUri) {
|
||||
this(resolver, Optional.ofNullable(baseUri), null);
|
||||
}
|
||||
|
||||
private PagedResourcesAssembler(@Nullable HateoasPageableHandlerMethodArgumentResolver resolver,
|
||||
Optional<UriComponents> baseUri, @Nullable MethodParameter parameter) {
|
||||
|
||||
this.pageableResolver = resolver == null ? new HateoasPageableHandlerMethodArgumentResolver() : resolver;
|
||||
this.baseUri = Optional.ofNullable(baseUri);
|
||||
this.baseUri = baseUri;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,6 +94,17 @@ public class PagedResourcesAssembler<T> implements RepresentationModelAssembler<
|
||||
this.forceFirstAndLastRels = forceFirstAndLastRels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PagedResourcesAssembler} with the given reference {@link MethodParameter}.
|
||||
*
|
||||
* @param parameter can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @since 3.1
|
||||
*/
|
||||
public PagedResourcesAssembler<T> withParameter(@Nullable MethodParameter parameter) {
|
||||
return new PagedResourcesAssembler<>(pageableResolver, baseUri, parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedModel<EntityModel<T>> toModel(Page<T> entity) {
|
||||
return toModel(entity, EntityModel::of);
|
||||
@@ -267,7 +285,7 @@ public class PagedResourcesAssembler<T> implements RepresentationModelAssembler<
|
||||
private Link createLink(UriTemplate base, Pageable pageable, LinkRelation relation) {
|
||||
|
||||
UriComponentsBuilder builder = fromUri(base.expand());
|
||||
pageableResolver.enhance(builder, getMethodParameter(), pageable);
|
||||
pageableResolver.enhance(builder, parameter, pageable);
|
||||
|
||||
return Link.of(UriTemplate.of(builder.build().toString()), relation);
|
||||
}
|
||||
@@ -278,8 +296,10 @@ public class PagedResourcesAssembler<T> implements RepresentationModelAssembler<
|
||||
*
|
||||
* @return
|
||||
* @since 1.7
|
||||
* @deprecated since 3.1, rather set up the instance with {@link #withParameter(MethodParameter)}.
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated(since = "3.1", forRemoval = true)
|
||||
protected MethodParameter getMethodParameter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -15,17 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.web;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.hateoas.server.core.MethodParameters;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
@@ -45,11 +35,6 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
*/
|
||||
public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(PagedResourcesAssemblerArgumentResolver.class);
|
||||
|
||||
private static final String SUPERFLOUS_QUALIFIER = "Found qualified %s parameter, but a unique unqualified %s parameter; Using that one, but you might want to check your controller method configuration";
|
||||
private static final String PARAMETER_AMBIGUITY = "Discovered multiple parameters of type Pageable but no qualifier annotations to disambiguate";
|
||||
|
||||
private final HateoasPageableHandlerMethodArgumentResolver resolver;
|
||||
|
||||
/**
|
||||
@@ -72,82 +57,7 @@ public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArg
|
||||
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
|
||||
|
||||
MethodParameter pageableParameter = findMatchingPageableParameter(parameter);
|
||||
|
||||
if (pageableParameter != null) {
|
||||
return new MethodParameterAwarePagedResourcesAssembler<>(pageableParameter, resolver, null);
|
||||
} else {
|
||||
return new PagedResourcesAssembler<>(resolver, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns finds the {@link MethodParameter} for a {@link Pageable} instance matching the given
|
||||
* {@link MethodParameter} requesting a {@link PagedResourcesAssembler}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private static MethodParameter findMatchingPageableParameter(MethodParameter parameter) {
|
||||
|
||||
Method method = parameter.getMethod();
|
||||
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException(String.format("Could not obtain method from parameter %s", parameter));
|
||||
}
|
||||
|
||||
MethodParameters parameters = MethodParameters.of(method);
|
||||
List<MethodParameter> pageableParameters = parameters.getParametersOfType(Pageable.class);
|
||||
Qualifier assemblerQualifier = parameter.getParameterAnnotation(Qualifier.class);
|
||||
|
||||
if (pageableParameters.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pageableParameters.size() == 1) {
|
||||
|
||||
MethodParameter pageableParameter = pageableParameters.get(0);
|
||||
MethodParameter matchingParameter = returnIfQualifiersMatch(pageableParameter, assemblerQualifier);
|
||||
|
||||
if (matchingParameter == null) {
|
||||
logger.info(LogMessage.format(SUPERFLOUS_QUALIFIER, PagedResourcesAssembler.class.getSimpleName(),
|
||||
Pageable.class.getName()));
|
||||
}
|
||||
|
||||
return pageableParameter;
|
||||
}
|
||||
|
||||
if (assemblerQualifier == null) {
|
||||
throw new IllegalStateException(PARAMETER_AMBIGUITY);
|
||||
}
|
||||
|
||||
for (MethodParameter pageableParameter : pageableParameters) {
|
||||
|
||||
MethodParameter matchingParameter = returnIfQualifiersMatch(pageableParameter, assemblerQualifier);
|
||||
|
||||
if (matchingParameter != null) {
|
||||
return matchingParameter;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(PARAMETER_AMBIGUITY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MethodParameter returnIfQualifiersMatch(MethodParameter pageableParameter,
|
||||
@Nullable Qualifier assemblerQualifier) {
|
||||
|
||||
if (assemblerQualifier == null) {
|
||||
return pageableParameter;
|
||||
}
|
||||
|
||||
Qualifier pageableParameterQualifier = pageableParameter.getParameterAnnotation(Qualifier.class);
|
||||
|
||||
if (pageableParameterQualifier == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pageableParameterQualifier.value().equals(assemblerQualifier.value()) ? pageableParameter : null;
|
||||
return new PagedResourcesAssembler<>(resolver, null)
|
||||
.withParameter(PageableMethodParameterUtils.findMatchingPageableParameter(parameter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
* Copyright 2022-2023 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.
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.web;
|
||||
|
||||
import static org.springframework.web.util.UriComponentsBuilder.fromUri;
|
||||
import static org.springframework.web.util.UriComponentsBuilder.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -26,8 +26,14 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.hateoas.*;
|
||||
import org.springframework.hateoas.EntityModel;
|
||||
import org.springframework.hateoas.IanaLinkRelations;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.LinkRelation;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.SlicedModel;
|
||||
import org.springframework.hateoas.SlicedModel.SliceMetadata;
|
||||
import org.springframework.hateoas.UriTemplate;
|
||||
import org.springframework.hateoas.server.RepresentationModelAssembler;
|
||||
import org.springframework.hateoas.server.core.EmbeddedWrapper;
|
||||
import org.springframework.hateoas.server.core.EmbeddedWrappers;
|
||||
@@ -38,62 +44,74 @@ import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* {@link RepresentationModelAssembler} to easily convert {@link Slice} instances into
|
||||
* {@link SlicedModel}.
|
||||
* {@link RepresentationModelAssembler} to easily convert {@link Slice} instances into {@link SlicedModel}.
|
||||
*
|
||||
* @author Michael Schout
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.1
|
||||
*/
|
||||
public class SlicedResourcesAssembler<T>
|
||||
implements RepresentationModelAssembler<Slice<T>, SlicedModel<EntityModel<T>>> {
|
||||
|
||||
private final HateoasPageableHandlerMethodArgumentResolver pageableResolver;
|
||||
|
||||
private final Optional<UriComponents> baseUri;
|
||||
private final EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
|
||||
|
||||
private boolean forceFirstRel = false;
|
||||
private @Nullable MethodParameter parameter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SlicedResourcesAssembler} using the given
|
||||
* {@link PageableHandlerMethodArgumentResolver} and base URI. If the former is
|
||||
* {@literal null}, a default one will be created. If the latter is {@literal null}, calls
|
||||
* to {@link #toModel(Slice)} will use the current request's URI to build the relevant
|
||||
* previous and next links.
|
||||
* Creates a new {@link SlicedResourcesAssembler} using the given {@link PageableHandlerMethodArgumentResolver} and
|
||||
* base URI. If the former is {@literal null}, a default one will be created. If the latter is {@literal null}, calls
|
||||
* to {@link #toModel(Slice)} will use the current request's URI to build the relevant previous and next links.
|
||||
*
|
||||
* @param resolver can be {@literal null}.
|
||||
* @param baseUri can be {@literal null}.
|
||||
*/
|
||||
public SlicedResourcesAssembler(@Nullable HateoasPageableHandlerMethodArgumentResolver resolver,
|
||||
@Nullable UriComponents baseUri) {
|
||||
this.pageableResolver = resolver == null ? new HateoasPageableHandlerMethodArgumentResolver() : resolver;
|
||||
this.baseUri = Optional.ofNullable(baseUri);
|
||||
this(resolver, Optional.ofNullable(baseUri), null);
|
||||
}
|
||||
|
||||
private static String currentRequest() {
|
||||
return ServletUriComponentsBuilder.fromCurrentRequest().build().toString();
|
||||
private SlicedResourcesAssembler(@Nullable HateoasPageableHandlerMethodArgumentResolver resolver,
|
||||
@Nullable Optional<UriComponents> baseUri, @Nullable MethodParameter parameter) {
|
||||
|
||||
this.pageableResolver = resolver == null ? new HateoasPageableHandlerMethodArgumentResolver() : resolver;
|
||||
this.baseUri = baseUri;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures whether to always add {@code first} links to the {@link SlicedModel} *
|
||||
* created. Defaults to {@literal false} which means that {@code first} links onlys appear
|
||||
* in conjunction with {@code prev} and {@code next} links.
|
||||
* Configures whether to always add {@code first} links to the {@link SlicedModel} * created. Defaults to
|
||||
* {@literal false} which means that {@code first} links onlys appear in conjunction with {@code prev} and
|
||||
* {@code next} links.
|
||||
*
|
||||
* @param forceFirstRel whether to always add {@code first} links to the
|
||||
* {@link SlicedModel} created.
|
||||
* @param forceFirstRel whether to always add {@code first} links to the {@link SlicedModel} created.
|
||||
*/
|
||||
public void setForceFirstRel(boolean forceFirstRel) {
|
||||
this.forceFirstRel = forceFirstRel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SlicedResourcesAssembler} with the given reference {@link MethodParameter}.
|
||||
*
|
||||
* @param parameter can be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @since 3.1
|
||||
*/
|
||||
public SlicedResourcesAssembler<T> withParameter(@Nullable MethodParameter parameter) {
|
||||
return new SlicedResourcesAssembler<>(pageableResolver, baseUri, parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SlicedModel<EntityModel<T>> toModel(Slice<T> entity) {
|
||||
return toModel(entity, EntityModel::of);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SlicedModel} by converting the given {@link Slice} into a
|
||||
* {@link SliceMetadata} instance and wrapping the contained elements into *
|
||||
* {@link SlicedModel} instances. Will add pagination links based on the given self link.
|
||||
* Creates a new {@link SlicedModel} by converting the given {@link Slice} into a {@link SliceMetadata} instance and
|
||||
* wrapping the contained elements into * {@link SlicedModel} instances. Will add pagination links based on the given
|
||||
* self link.
|
||||
*
|
||||
* @param slice must not be {@literal null}.
|
||||
* @param selfLink must not be {@literal null}.
|
||||
@@ -104,9 +122,8 @@ public class SlicedResourcesAssembler<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SlicedModel} by converting the given {@link Slice} into a
|
||||
* {@link SliceMetadata} instance and using the given {@link SlicedModel} to turn elements
|
||||
* of the {@link Slice} into resources.
|
||||
* Creates a new {@link SlicedModel} by converting the given {@link Slice} into a {@link SliceMetadata} instance and
|
||||
* using the given {@link SlicedModel} to turn elements of the {@link Slice} into resources.
|
||||
*
|
||||
* @param slice must not be {@literal null}.
|
||||
* @param assembler must not be {@literal null}.
|
||||
@@ -118,10 +135,9 @@ public class SlicedResourcesAssembler<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SlicedModel} by converting the given {@link Slice} into a
|
||||
* {@link SliceMetadata} instance and using the given {@link SlicedModel} to turn elements
|
||||
* of the {@link Slice} into resources. Will add pagination links based on the given the
|
||||
* self link.
|
||||
* Creates a new {@link SlicedModel} by converting the given {@link Slice} into a {@link SliceMetadata} instance and
|
||||
* using the given {@link SlicedModel} to turn elements of the {@link Slice} into resources. Will add pagination links
|
||||
* based on the given the self link.
|
||||
*
|
||||
* @param slice must not be {@literal null}.
|
||||
* @param assembler must not be {@literal null}.
|
||||
@@ -134,8 +150,7 @@ public class SlicedResourcesAssembler<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SlicedModel} with an empty collection {@link EmbeddedWrapper} for the
|
||||
* given domain type.
|
||||
* Creates a {@link SlicedModel} with an empty collection {@link EmbeddedWrapper} for the given domain type.
|
||||
*
|
||||
* @param slice must not be {@literal null}, content must be empty.
|
||||
* @param type must not be {@literal null}.
|
||||
@@ -146,8 +161,7 @@ public class SlicedResourcesAssembler<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SlicedModel} with an empty collection {@link EmbeddedWrapper} for the
|
||||
* given domain type.
|
||||
* Creates a {@link SlicedModel} with an empty collection {@link EmbeddedWrapper} for the given domain type.
|
||||
*
|
||||
* @param slice must not be {@literal null}, content must be empty.
|
||||
* @param type must not be {@literal null}.
|
||||
@@ -159,6 +173,7 @@ public class SlicedResourcesAssembler<T>
|
||||
}
|
||||
|
||||
public SlicedModel<?> toEmptyModel(Slice<?> slice, Class<?> type, Optional<Link> link) {
|
||||
|
||||
Assert.notNull(slice, "Slice must not be null");
|
||||
Assert.isTrue(!slice.hasContent(), "Slice must not have any content");
|
||||
Assert.notNull(type, "Type must not be null");
|
||||
@@ -175,8 +190,7 @@ public class SlicedResourcesAssembler<T>
|
||||
/**
|
||||
* Creates the {@link SlicedModel} to be equipped with pagination links downstream.
|
||||
*
|
||||
* @param resources the original slices's elements mapped into {@link RepresentationModel}
|
||||
* instances.
|
||||
* @param resources the original slices's elements mapped into {@link RepresentationModel} instances.
|
||||
* @param metadata the calculated {@link SliceMetadata}, must not be {@literal null}.
|
||||
* @param slice the original page handed to the assembler, must not be {@literal null}.
|
||||
* @return must not be {@literal null}.
|
||||
@@ -233,8 +247,8 @@ public class SlicedResourcesAssembler<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a default URI string either from the one configured on then assembler or by
|
||||
* looking it up from the current request.
|
||||
* Returns a default URI string either from the one configured on then assembler or by looking it up from the current
|
||||
* request.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -243,9 +257,8 @@ public class SlicedResourcesAssembler<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Link} with the given {@link LinkRelation} that will be based on the
|
||||
* given {@link UriTemplate} but enriched with the values of the given {@link Pageable}
|
||||
* (if not {@literal null}).
|
||||
* Creates a {@link Link} with the given {@link LinkRelation} that will be based on the given {@link UriTemplate} but
|
||||
* enriched with the values of the given {@link Pageable} (if not {@literal null}).
|
||||
*
|
||||
* @param base must not be {@literal null}.
|
||||
* @param pageable can be {@literal null}
|
||||
@@ -254,23 +267,11 @@ public class SlicedResourcesAssembler<T>
|
||||
*/
|
||||
private Link createLink(UriTemplate base, Pageable pageable, LinkRelation relation) {
|
||||
UriComponentsBuilder builder = fromUri(base.expand());
|
||||
pageableResolver.enhance(builder, getMethodParameter(), pageable);
|
||||
pageableResolver.enhance(builder, parameter, pageable);
|
||||
|
||||
return Link.of(UriTemplate.of(builder.build().toString()), relation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link MethodParameter} to be used to potentially qualify the paging and
|
||||
* sorting request parameters to. Default implementations returns {@literal null}, which
|
||||
* means the parameters will not be qualified.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
protected MethodParameter getMethodParameter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SliceMetadata} instance from the given {@link Slice}.
|
||||
*
|
||||
@@ -288,4 +289,8 @@ public class SlicedResourcesAssembler<T>
|
||||
private String baseUriOrCurrentRequest() {
|
||||
return baseUri.map(Object::toString).orElseGet(SlicedResourcesAssembler::currentRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private static String currentRequest() {
|
||||
return ServletUriComponentsBuilder.fromCurrentRequest().build().toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
* Copyright 2022-2023 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.
|
||||
@@ -15,16 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.web;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.hateoas.server.core.MethodParameters;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
@@ -37,12 +28,10 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
* controller methods.
|
||||
*
|
||||
* @author Michael Schout
|
||||
* @author Oliver Drotbohm
|
||||
* @since 3.1
|
||||
*/
|
||||
public class SlicedResourcesAssemblerArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
private static final Log logger = LogFactory.getLog(SlicedResourcesAssemblerArgumentResolver.class);
|
||||
|
||||
private static final String SUPERFLOUS_QUALIFIER = "Found qualified %s parameter, but a unique unqualified %s parameter; Using that one, but you might want to check your controller method configuration";
|
||||
private static final String PARAMETER_AMBIGUITY = "Discovered multiple parameters of type Pageable but no qualifier annotations to disambiguate";
|
||||
|
||||
private final HateoasPageableHandlerMethodArgumentResolver resolver;
|
||||
|
||||
@@ -56,73 +45,6 @@ public class SlicedResourcesAssemblerArgumentResolver implements HandlerMethodAr
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns finds the {@link MethodParameter} for a {@link Pageable} instance matching the
|
||||
* given {@link MethodParameter} requesting a {@link SlicedResourcesAssembler}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private static MethodParameter findMatchingPageableParameter(MethodParameter parameter) {
|
||||
Method method = parameter.getMethod();
|
||||
|
||||
if (method == null) {
|
||||
throw new IllegalArgumentException(String.format("Could not obtain method from parameter %s", parameter));
|
||||
}
|
||||
|
||||
MethodParameters parameters = MethodParameters.of(method);
|
||||
List<MethodParameter> pageableParameters = parameters.getParametersOfType(Pageable.class);
|
||||
Qualifier assemblerQualifier = parameter.getParameterAnnotation(Qualifier.class);
|
||||
|
||||
if (pageableParameters.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pageableParameters.size() == 1) {
|
||||
MethodParameter pageableParameter = pageableParameters.get(0);
|
||||
MethodParameter matchingParameter = returnIfQualifiersMatch(pageableParameter, assemblerQualifier);
|
||||
|
||||
if (matchingParameter == null) {
|
||||
logger.info(LogMessage.format(SUPERFLOUS_QUALIFIER, SlicedResourcesAssembler.class.getSimpleName(),
|
||||
Pageable.class.getName()));
|
||||
}
|
||||
|
||||
return pageableParameter;
|
||||
}
|
||||
|
||||
if (assemblerQualifier == null) {
|
||||
throw new IllegalStateException(PARAMETER_AMBIGUITY);
|
||||
}
|
||||
|
||||
for (MethodParameter pageableParameter : pageableParameters) {
|
||||
MethodParameter matchingParameter = returnIfQualifiersMatch(pageableParameter, assemblerQualifier);
|
||||
|
||||
if (matchingParameter != null) {
|
||||
return matchingParameter;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalStateException(PARAMETER_AMBIGUITY);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MethodParameter returnIfQualifiersMatch(MethodParameter pageableParameter,
|
||||
@Nullable Qualifier assemblerQualifier) {
|
||||
|
||||
if (assemblerQualifier == null) {
|
||||
return pageableParameter;
|
||||
}
|
||||
|
||||
Qualifier pageableParameterQualifier = pageableParameter.getParameterAnnotation(Qualifier.class);
|
||||
|
||||
if (pageableParameterQualifier == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return pageableParameterQualifier.value().equals(assemblerQualifier.value()) ? pageableParameter : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return SlicedResourcesAssembler.class.equals(parameter.getParameterType());
|
||||
@@ -133,13 +55,7 @@ public class SlicedResourcesAssemblerArgumentResolver implements HandlerMethodAr
|
||||
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
|
||||
|
||||
MethodParameter pageableParameter = findMatchingPageableParameter(parameter);
|
||||
|
||||
if (pageableParameter != null) {
|
||||
return new MethodParameterAwareSlicedResourcesAssembler<>(pageableParameter, resolver, null);
|
||||
}
|
||||
else {
|
||||
return new SlicedResourcesAssembler<>(resolver, null);
|
||||
}
|
||||
return new SlicedResourcesAssembler<>(resolver, null) //
|
||||
.withParameter(PageableMethodParameterUtils.findMatchingPageableParameter(parameter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,14 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
* @author Vedran Pavic
|
||||
* @author Mark Paluch
|
||||
* @author Greg Turnquist
|
||||
* @author Michael Schout
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class HateoasAwareSpringDataWebConfiguration extends SpringDataWebConfiguration {
|
||||
|
||||
private final Lazy<HateoasSortHandlerMethodArgumentResolver> sortResolver;
|
||||
private final Lazy<HateoasPageableHandlerMethodArgumentResolver> pageableResolver;
|
||||
private final Lazy<PagedResourcesAssemblerArgumentResolver> argumentResolver;
|
||||
private final Lazy<PagedResourcesAssemblerArgumentResolver> pagedResourcesArgumentResolver;
|
||||
private final Lazy<SlicedResourcesAssemblerArgumentResolver> slicedResourcesArgumentResolver;
|
||||
|
||||
/**
|
||||
@@ -65,7 +66,7 @@ public class HateoasAwareSpringDataWebConfiguration extends SpringDataWebConfigu
|
||||
.of(() -> context.getBean("sortResolver", HateoasSortHandlerMethodArgumentResolver.class));
|
||||
this.pageableResolver = Lazy
|
||||
.of(() -> context.getBean("pageableResolver", HateoasPageableHandlerMethodArgumentResolver.class));
|
||||
this.argumentResolver = Lazy.of(() -> context.getBean("pagedResourcesAssemblerArgumentResolver",
|
||||
this.pagedResourcesArgumentResolver = Lazy.of(() -> context.getBean("pagedResourcesAssemblerArgumentResolver",
|
||||
PagedResourcesAssemblerArgumentResolver.class));
|
||||
this.slicedResourcesArgumentResolver = Lazy.of(() -> context.getBean("slicedResourcesAssemblerArgumentResolver",
|
||||
SlicedResourcesAssemblerArgumentResolver.class));
|
||||
@@ -115,7 +116,7 @@ public class HateoasAwareSpringDataWebConfiguration extends SpringDataWebConfigu
|
||||
|
||||
super.addArgumentResolvers(argumentResolvers);
|
||||
|
||||
argumentResolvers.add(argumentResolver.get());
|
||||
argumentResolvers.add(pagedResourcesArgumentResolver.get());
|
||||
argumentResolvers.add(slicedResourcesArgumentResolver.get());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user