Skip to content

Commit

Permalink
DATACMNS-418 - PagedResourcesAssembler now adds self links to PagedRe…
Browse files Browse the repository at this point in the history
…sources.

PagedResourcesAssembler now adds a LinkTemplate to every PagedResources instance created and pulls the template variable information from the registered resolvers for Pageable and Sort.

Extended PageableResourceAssemblerArgumentResolver to create a special MethodParameterAwarePagedResourcesAssembler so that the assembler will take qualifier information into account when being injected into controller methods.

Upgraded to Spring HATEOAS 0.9.0.BUILD-SNAPSHOT to be able to benefit from new methods added to MethodParameters as well as the LinkTemplate class. Updated Sonargraph architecture definition to allow the web layer to access logging.
  • Loading branch information
odrotbohm committed Jan 16, 2014
1 parent 9db6cca commit 92a2218
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 11 deletions.
1 change: 1 addition & 0 deletions Spring Data Commons.sonargraph
Expand Up @@ -16,6 +16,7 @@
<dependency toName="Project|spring-data-commons::Layer|Application" type="AllowedDependency"/>
<dependency toName="Project|spring-data-commons::Layer|Repositories" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Java Beans" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Logging" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Reflection" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Servlet API" type="AllowedDependency"/>
<dependency toName="External|External::Subsystem|Spring" type="AllowedDependency"/>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -19,7 +19,7 @@
<properties>
<jackson>1.9.7</jackson>
<jackson2>2.2.2</jackson2>
<springhateoas>0.8.0.RELEASE</springhateoas>
<springhateoas>0.9.0.BUILD-SNAPSHOT</springhateoas>
<dist.key>DATACMNS</dist.key>
</properties>

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
Expand Down Expand Up @@ -76,9 +76,20 @@ public HateoasPageableHandlerMethodArgumentResolver(HateoasSortHandlerMethodArgu
this.sortResolver = getDefaultedSortResolver(sortResolver);
}

private static HateoasSortHandlerMethodArgumentResolver getDefaultedSortResolver(
HateoasSortHandlerMethodArgumentResolver sortResolver) {
return sortResolver == null ? DEFAULT_SORT_RESOLVER : sortResolver;
/**
* Returns the template variable for the pagination parameters.
*
* @param parameter can be {@literal null}.
* @return
* @since 1.7
*/
public String getPaginationTemplateVariables(MethodParameter parameter) {

String pagePropertyName = getParameterNameToUse(getPageParameterName(), parameter);
String sizePropertyName = getParameterNameToUse(getSizeParameterName(), parameter);

String sortTemplateVariables = sortResolver.getSortTemplateVariables(parameter);
return String.format("{?%s,%s}{&%s", pagePropertyName, sizePropertyName, sortTemplateVariables.substring(2));
}

/*
Expand All @@ -105,4 +116,9 @@ public void enhance(UriComponentsBuilder builder, MethodParameter parameter, Obj

this.sortResolver.enhance(builder, parameter, pageable.getSort());
}

private static HateoasSortHandlerMethodArgumentResolver getDefaultedSortResolver(
HateoasSortHandlerMethodArgumentResolver sortResolver) {
return sortResolver == null ? DEFAULT_SORT_RESOLVER : sortResolver;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
Expand Down Expand Up @@ -35,6 +35,17 @@
public class HateoasSortHandlerMethodArgumentResolver extends SortHandlerMethodArgumentResolver implements
UriComponentsContributor {

/**
* Returns the tempalte variables for the sort parameter.
*
* @param parameter must not be {@literal null}.
* @return
* @since 1.7
*/
public String getSortTemplateVariables(MethodParameter parameter) {
return String.format("{?%s}", getSortParameter(parameter));
}

/*
* (non-Javadoc)
* @see org.springframework.hateoas.mvc.UriComponentsContributor#enhance(org.springframework.web.util.UriComponentsBuilder, org.springframework.core.MethodParameter, java.lang.Object)
Expand Down
@@ -0,0 +1,57 @@
/*
* Copyright 2014 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.data.web;

import org.springframework.core.MethodParameter;
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,
HateoasPageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) {

super(resolver, baseUri);

Assert.notNull(parameter, "Method parameter must not be null!");
this.parameter = parameter;
}

/*
* (non-Javadoc)
* @see org.springframework.data.web.PagedResourcesAssembler#getMethodParameter()
*/
@Override
protected MethodParameter getMethodParameter() {
return parameter;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
Expand All @@ -20,9 +20,11 @@
import java.util.ArrayList;
import java.util.List;

import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkTemplate;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.Resource;
Expand Down Expand Up @@ -141,16 +143,30 @@ private <R extends ResourceSupport> PagedResources<R> addPaginationLinks(PagedRe
foo(resources, page.previousPageable(), uri, Link.REL_PREVIOUS);
}

resources.add(new LinkTemplate(uri + pageableResolver.getPaginationTemplateVariables(getMethodParameter()),
Link.REL_SELF));

return resources;
}

private void foo(PagedResources<?> resources, Pageable pageable, String uri, String rel) {

UriComponentsBuilder builder = fromUriString(uri);
pageableResolver.enhance(builder, null, pageable);
pageableResolver.enhance(builder, getMethodParameter(), pageable);
resources.add(new Link(builder.build().toUriString(), rel));
}

/**
* 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
* @since 1.7
*/
protected MethodParameter getMethodParameter() {
return null;
}

/**
* Creates a new {@link PageMetadata} instance from the given {@link Page}.
*
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
Expand All @@ -15,9 +15,16 @@
*/
package org.springframework.data.web;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MethodLinkBuilderFactory;
import org.springframework.hateoas.core.MethodParameters;
import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
Expand All @@ -36,6 +43,11 @@
*/
public class PagedResourcesAssemblerArgumentResolver implements HandlerMethodArgumentResolver {

private static final Logger LOGGER = LoggerFactory.getLogger(PagedResourcesAssemblerArgumentResolver.class);

private static final String SUPERFLOUS_QUALIFIER = "Found qualified {} parameter, but a unique unqualified {} parameter. Using that one, but you might wanna check your controller method configuration!";
private static final String PARAMETER_AMBIGUITY = "Discovered muliple parameters of type Pageable but no qualifier annotations to disambiguate!";

private final HateoasPageableHandlerMethodArgumentResolver resolver;
private final MethodLinkBuilderFactory<?> linkBuilderFactory;

Expand Down Expand Up @@ -73,6 +85,72 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
Link linkToMethod = linkBuilderFactory.linkTo(parameter.getMethod(), new Object[0]).withSelfRel();
UriComponents fromUriString = UriComponentsBuilder.fromUriString(linkToMethod.getHref()).build();

return new PagedResourcesAssembler<Object>(resolver, fromUriString);
MethodParameter pageableParameter = findMatchingPageableParameter(parameter);

if (pageableParameter != null) {
return new MethodParameterAwarePagedResourcesAssembler<Object>(pageableParameter, resolver, fromUriString);
} else {
return new PagedResourcesAssembler<Object>(resolver, fromUriString);
}
}

/**
* 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
*/
private static final MethodParameter findMatchingPageableParameter(MethodParameter parameter) {

MethodParameters parameters = new MethodParameters(parameter.getMethod());
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(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);
}

private static MethodParameter returnIfQualifiersMatch(MethodParameter pageableParameter, 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;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* Copyright 2013-2014 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.
Expand Down Expand Up @@ -76,6 +76,19 @@ public void preventsPageSizeFromExceedingMayValueIfConfiguredOnWrite() throws Ex
assertUriStringFor(new PageRequest(0, 200), "page=0&size=100");
}

/**
* @see DATACMNS-418
*/
@Test
public void returnCorrectTemplateVariables() {

HateoasPageableHandlerMethodArgumentResolver resolver = getResolver();
assertThat(resolver.getPaginationTemplateVariables(null), is("{?page,size}{&sort}"));

resolver.setPageParameterName("foo");
assertThat(resolver.getPaginationTemplateVariables(null), is("{?foo,size}{&sort}"));
}

@Override
protected HateoasPageableHandlerMethodArgumentResolver getResolver() {

Expand Down
Expand Up @@ -51,6 +51,16 @@ public void replacesExistingRequestParameters() throws Exception {
assertUriStringFor(SORT, "/?sort=firstname,lastname,desc", "/?sort=foo,asc");
}

/**
* @see DATACMNS-418
*/
@Test
public void returnCorrectTemplateVariables() {

HateoasSortHandlerMethodArgumentResolver resolver = new HateoasSortHandlerMethodArgumentResolver();
assertThat(resolver.getSortTemplateVariables(null), is("{?sort}"));
}

private void assertUriStringFor(Sort sort, String expected) throws Exception {
assertUriStringFor(sort, expected, "/");
}
Expand Down

0 comments on commit 92a2218

Please sign in to comment.