Skip to content

Commit

Permalink
DATAREST-217 - Significant overhaul of HTTP method support detection.
Browse files Browse the repository at this point in the history
Refactored the way the general support for an HTTP method for the resource exported. The decision is implemented in RootResourceInformation (formerly RepositoryRestRequest). Removed request specific information from that class and introduced a HandlerMethodArgumentResolver to be able to inject HttpMethod instance into controller methods (filed https://jira.springsource.org/browse/SPR-11425 to get that support into Spring Framework itself).

Generally moved away from throwing NoSuchMethodExceptions and correctly expose HttpRequestMethodNotSupportedException instead to make sure Spring MVC renders the appropriate allowed methods if possible.

Removed RepositoryInvokerHandlerMethodArgumentResolver as a RepositoryInvoker can be obtained from the RootResourceInformation where necessary.

Related pull request: spring-projects#125.
  • Loading branch information
odrotbohm committed Feb 14, 2014
1 parent ef1ee10 commit 922827f
Show file tree
Hide file tree
Showing 24 changed files with 815 additions and 253 deletions.
Original file line number Diff line number Diff line change
@@ -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 @@ -72,6 +72,15 @@ public ReflectionRepositoryInvoker(Object repository, RepositoryInformation info
this.conversionService = conversionService;
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindAllMethod()
*/
@Override
public boolean hasFindAllMethod() {
return methods.hasFindAllMethod();
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesFindAll()
Expand Down Expand Up @@ -115,6 +124,15 @@ public Iterable<Object> invokeFindAll(Pageable pageable) {
return invoke(method, pageable);
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasSaveMethod()
*/
@Override
public boolean hasSaveMethod() {
return methods.hasSaveMethod();
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesSave()
Expand All @@ -132,6 +150,15 @@ public <T> T invokeSave(T object) {
return invoke(methods.getSaveMethod(), object);
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindOneMethod()
*/
@Override
public boolean hasFindOneMethod() {
return methods.hasFindOneMethod();
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesFindOne()
Expand All @@ -150,6 +177,15 @@ public <T> T invokeFindOne(Serializable id) {
return invoke(methods.getFindOneMethod(), convertId(id));
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasDeleteMethod()
*/
@Override
public boolean hasDeleteMethod() {
return methods.hasDelete();
}

/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesDelete()
Expand Down
Original file line number Diff line number Diff line change
@@ -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 @@ -22,11 +22,59 @@
*/
public interface RepositoryInvocationInformation {

/**
* Returns whether the repository has a method to save objects.
*
* @return
*/
boolean hasSaveMethod();

/**
* Returns whether the repository exposes the save method.
*
* @return
*/
boolean exposesSave();

/**
* Returns whether the repository has a method to delete objects.
*
* @return
*/
boolean hasDeleteMethod();

/**
* Returns whether the repository exposes the delete method.
*
* @return
*/
boolean exposesDelete();

/**
* Returns whether the repository has a method to find a single object.
*
* @return
*/
boolean hasFindOneMethod();

/**
* Returns whether the repository exposes the method to find a single object.
*
* @return
*/
boolean exposesFindOne();

/**
* Returns whether the repository has a method to find all objects.
*
* @return
*/
boolean hasFindAllMethod();

/**
* Returns whether the repository exposes the method to find all objects.
*
* @return
*/
boolean exposesFindAll();
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
Expand Down Expand Up @@ -108,12 +109,6 @@ public ResponseEntity<?> handleNotFound() {
return notFound();
}

@ExceptionHandler({ NoSuchMethodError.class, HttpRequestMethodNotSupportedException.class })
@ResponseBody
public ResponseEntity<?> handleNoSuchMethod() {
return errorResponse(null, HttpStatus.METHOD_NOT_ALLOWED);
}

@ExceptionHandler({ HttpMessageNotReadableException.class, HttpMessageNotWritableException.class })
@ResponseBody
public ResponseEntity<ExceptionMessage> handleNotReadable(HttpMessageNotReadableException e) {
Expand Down Expand Up @@ -157,6 +152,22 @@ public ResponseEntity handleConflict(Exception ex) {
return errorResponse(null, ex, HttpStatus.CONFLICT);
}

/**
* Send {@code 405 Method Not Allowed} and include the supported {@link HttpMethod}s in the {@code Allow} header.
*
* @param o_O
* @return
*/
@ExceptionHandler
@ResponseBody
public ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {

HttpHeaders headers = new HttpHeaders();
headers.setAllow(o_O.getSupportedHttpMethods());

return new ResponseEntity<Void>(headers, HttpStatus.METHOD_NOT_ALLOWED);
}

protected <T> ResponseEntity<T> notFound() {
return notFound(null, null);
}
Expand Down Expand Up @@ -195,9 +206,9 @@ public <T> ResponseEntity<T> response(HttpHeaders headers, T body, HttpStatus st
return new ResponseEntity<T>(body, hdrs, status);
}

protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) {
protected Link resourceLink(RootResourceInformation resourceLink, Resource resource) {

ResourceMetadata repoMapping = repoRequest.getResourceMetadata();
ResourceMetadata repoMapping = resourceLink.getResourceMetadata();

Link selfLink = resource.getLink("self");
String rel = repoMapping.getItemResourceRel();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2012-2013 the original author or authors.
* Copyright 2012-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 @@ -41,21 +41,22 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha
private static final String ERROR_MESSAGE = "Could not read an object of type %s from the request! Converter %s returned null!";
private static final String NO_CONVERTER_FOUND = "No suitable HttpMessageConverter found to read request body into object of type %s from request with content type of %s!";

private final RepositoryRestRequestHandlerMethodArgumentResolver repoRequestResolver;
private final RootResourceInformationHandlerMethodArgumentResolver repoRequestResolver;
private final List<HttpMessageConverter<?>> messageConverters;

/**
* Creates a new {@link PersistentEntityResourceHandlerMethodArgumentResolver} for the given
* {@link HttpMessageConverter}s and {@link RepositoryRestRequestHandlerMethodArgumentResolver}..
* {@link HttpMessageConverter}s and {@link RootResourceInformationHandlerMethodArgumentResolver}..
*
* @param messageConverters must not be {@literal null}.
* @param repositoryRequestResolver must not be {@literal null}.
*/
public PersistentEntityResourceHandlerMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
RepositoryRestRequestHandlerMethodArgumentResolver repositoryRequestResolver) {
RootResourceInformationHandlerMethodArgumentResolver repositoryRequestResolver) {

Assert.notEmpty(messageConverters, "MessageConverters must not be null or empty!");
Assert.notNull(repositoryRequestResolver, "RepositoryRestRequestHandlerMethodArgumentResolver must not be empty!");
Assert
.notNull(repositoryRequestResolver, "RootResourceInformationHandlerMethodArgumentResolver must not be empty!");

this.messageConverters = messageConverters;
this.repoRequestResolver = repositoryRequestResolver;
Expand All @@ -78,13 +79,14 @@ public boolean supportsParameter(MethodParameter parameter) {
@SuppressWarnings({ "unchecked", "rawtypes" })
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RepositoryRestRequest repoRequest = (RepositoryRestRequest) repoRequestResolver.resolveArgument(parameter,
mavContainer, webRequest, binderFactory);

RootResourceInformation resourceInformation = repoRequestResolver.resolveArgument(parameter, mavContainer,
webRequest, binderFactory);

HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class);
ServletServerHttpRequest request = new ServletServerHttpRequest(nativeRequest);

Class<?> domainType = repoRequest.getPersistentEntity().getType();
Class<?> domainType = resourceInformation.getPersistentEntity().getType();
MediaType contentType = request.getHeaders().getContentType();

for (HttpMessageConverter converter : messageConverters) {
Expand All @@ -99,7 +101,7 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m
throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, domainType, converter));
}

return new PersistentEntityResource<Object>(repoRequest.getPersistentEntity(), obj);
return new PersistentEntityResource<Object>(resourceInformation.getPersistentEntity(), obj);
}

throw new HttpMessageNotReadableException(String.format(NO_CONVERTER_FOUND, domainType, contentType));
Expand Down
Loading

0 comments on commit 922827f

Please sign in to comment.