Skip to content

Experiment: Detect link methods that do NOT return resources #629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,12 @@
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MethodLinkBuilderFactory;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.core.AnnotationAttribute;
Expand All @@ -44,6 +47,7 @@
import org.springframework.hateoas.core.MethodParameters;
import org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor.BoundMethodParameter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -67,6 +71,8 @@
*/
public class ControllerLinkBuilderFactory implements MethodLinkBuilderFactory<ControllerLinkBuilder> {

private static final Logger log = LoggerFactory.getLogger(ControllerLinkBuilderFactory.class);

private static final MappingDiscoverer DISCOVERER = new AnnotationMappingDiscoverer(RequestMapping.class);
private static final AnnotatedParametersParameterAccessor PATH_VARIABLE_ACCESSOR = new AnnotatedParametersParameterAccessor(
new AnnotationAttribute(PathVariable.class));
Expand Down Expand Up @@ -135,6 +141,20 @@ public ControllerLinkBuilder linkTo(Object invocationValue) {
Iterator<Object> classMappingParameters = invocations.getObjectParameters();
Method method = invocation.getMethod();

if (!ClassUtils.isAssignable(ResourceSupport.class, method.getReturnType())) {
log.warn("");
log.warn(method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()'s return type is " + method.getReturnType().toString() + ", which doesn't implement ResourceSupport, Resource, or Resources.");
log.warn("Return that from a controller and it probably won't serialize correctly.");
log.warn("");

System.out.println("");
System.out.println(method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()'s return type is " + method.getReturnType().toString() + ", which doesn't implement ResourceSupport, Resource, or Resources.");
System.out.println("Return that from a controller and it probably won't serialize correctly.");
System.out.println("");

throw new RuntimeException(method.getReturnType().toString() + " does NOT implement ResourceSupport");
}

String mapping = DISCOVERER.getMapping(invocation.getTargetType(), method);
UriComponentsBuilder builder = ControllerLinkBuilder.getBuilder().path(mapping);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.TestUtils;
import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.ControllerWithMethods;
import org.springframework.hateoas.mvc.ControllerLinkBuilderUnitTest.PersonControllerImpl;
Expand Down Expand Up @@ -174,19 +175,19 @@ public void createsLinkToParameterizedControllerRootWithParameterMap() {
assertThat(link.getHref(), endsWith("/people/17/addresses"));
}

static interface SampleController {
interface SampleController {

@RequestMapping("/sample/{id}")
HttpEntity<?> sampleMethod(@PathVariable("id") Long id, SpecialType parameter);
Resource<HttpEntity<?>> sampleMethod(@PathVariable("id") Long id, SpecialType parameter);

@RequestMapping("/sample/{time}")
HttpEntity<?> sampleMethod(@PathVariable("time") @DateTimeFormat(iso = ISO.DATE) DateTime time);
Resource<HttpEntity<?>> sampleMethod(@PathVariable("time") @DateTimeFormat(iso = ISO.DATE) DateTime time);

@RequestMapping("/sample/mapsupport")
HttpEntity<?> sampleMethodWithMap(@RequestParam Map<String, String> queryParams);
Resource<HttpEntity<?>> sampleMethodWithMap(@RequestParam Map<String, String> queryParams);

@RequestMapping("/sample/multivaluemapsupport")
HttpEntity<?> sampleMethodWithMap(@RequestParam MultiValueMap<String, String> queryParams);
Resource<HttpEntity<?>> sampleMethodWithMap(@RequestParam MultiValueMap<String, String> queryParams);
}

static class SampleUriComponentsContributor implements UriComponentsContributor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.mockito.Mockito;
import org.springframework.hateoas.Identifiable;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.TemplateVariable.VariableType;
import org.springframework.hateoas.TestUtils;
Expand Down Expand Up @@ -577,7 +578,7 @@ class PersonControllerImpl implements PersonController {}
static class PersonsAddressesController {

@RequestMapping("/{country}")
public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) {
public Resource<HttpEntity<Void>> getAddressesForCountry(@PathVariable String country) {
return null;
}
}
Expand All @@ -595,39 +596,39 @@ class UnmappedController {
static class ControllerWithMethods {

@RequestMapping("/else")
HttpEntity<Void> myMethod(@RequestBody Object payload) {
Resource<HttpEntity<Void>> myMethod(@RequestBody Object payload) {
return null;
}

@RequestMapping("/{id}/foo")
HttpEntity<Void> methodWithPathVariable(@PathVariable String id) {
Resource<HttpEntity<Void>> methodWithPathVariable(@PathVariable String id) {
return null;
}

@RequestMapping("/foo")
HttpEntity<Void> methodWithRequestParam(@RequestParam String id) {
Resource<HttpEntity<Void>> methodWithRequestParam(@RequestParam String id) {
return null;
}

@RequestMapping(value = "/{id}/foo")
HttpEntity<Void> methodForNextPage(@PathVariable String id, @RequestParam(required = false) Integer offset,
Resource<HttpEntity<Void>> methodForNextPage(@PathVariable String id, @RequestParam(required = false) Integer offset,
@RequestParam Integer limit) {
return null;
}

@RequestMapping(value = "/{id}/foo")
HttpEntity<Void> methodWithMultiValueRequestParams(@PathVariable String id, @RequestParam List<Integer> items,
Resource<HttpEntity<Void>> methodWithMultiValueRequestParams(@PathVariable String id, @RequestParam List<Integer> items,
@RequestParam Integer limit) {
return null;
}

@RequestMapping(value = "/foo")
HttpEntity<Void> methodForOptionalNextPage(@RequestParam(required = false) Integer offset) {
Resource<HttpEntity<Void>> methodForOptionalNextPage(@RequestParam(required = false) Integer offset) {
return null;
}

@RequestMapping(value = "/bar")
HttpEntity<Void> methodForOptionalSizeWithDefaultValue(@RequestParam(defaultValue = "10") Integer size) {
Resource<HttpEntity<Void>> methodForOptionalSizeWithDefaultValue(@RequestParam(defaultValue = "10") Integer size) {
return null;
}
}
Expand All @@ -638,13 +639,13 @@ interface ParentController {}
interface ChildController extends ParentController {

@RequestMapping("/child")
Object myMethod();
Resource<Object> myMethod();
}

interface ParentWithMethod {

@RequestMapping("/parent")
Object myMethod();
Resource<Object> myMethod();
}

@RequestMapping("/child")
Expand All @@ -653,7 +654,7 @@ interface ChildWithTypeMapping extends ParentWithMethod {}
interface ParentControllerWithoutRootMapping {

@RequestMapping
Object someEmptyMappedMethod();
Resource<Object> someEmptyMappedMethod();
}

@RequestMapping("/root")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.springframework.hateoas.mvc;

import org.junit.Test;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.TestUtils;
import org.springframework.hateoas.core.DummyInvocationUtils;
import org.springframework.http.HttpEntity;
Expand All @@ -40,8 +41,8 @@ public void test() {
static class SampleController {

@RequestMapping("/{id}/foo")
HttpEntity<Void> someMethod(@PathVariable("id") Long id) {
return new ResponseEntity<Void>(HttpStatus.OK);
Resource<HttpEntity<Void>> someMethod(@PathVariable("id") Long id) {
return new Resource(new ResponseEntity<Void>(HttpStatus.OK));
}
}
}
3 changes: 2 additions & 1 deletion template.mf
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ Import-Template:
org.aopalliance.*;version="[1.0.0,2.0.0)";resolution:=optional,
org.atteo.evo.inflector.*;version="${evo.version:[=.=.=,+1.0.0)}";resolution:=optional,
org.springframework.plugin.*;version="[0.8.0,2.0.0)";resolution:=optional,
org.springframework.*;version="${spring.version:[=.=.=,+1.1.0)}";resolution:=optional
org.springframework.*;version="${spring.version:[=.=.=,+1.1.0)}";resolution:=optional,
org.slf4j.*;version="[1.7.22,1.7.22]";resolution:=optional