Browse Source
RequestBodyAdvice is analogous to ResponseBodyAdvice (added in 4.1) but for intercepting for reading the request with an HttpMessageConverter for resolving an @RequestBody or an HttpEntity method argument. Issue: SPR-12501pull/566/merge
12 changed files with 512 additions and 174 deletions
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
/* |
||||
* Copyright 2002-2015 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.web.servlet.mvc.method.annotation; |
||||
|
||||
import java.lang.reflect.Type; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.http.HttpInputMessage; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
|
||||
|
||||
/** |
||||
* Allows customizing the request before its body is read and converted into an |
||||
* Object and also allows the resulting Object before it is passed into a |
||||
* controller method as an {code @RequestBody} or an {@code HttpEntity} method |
||||
* argument. |
||||
* |
||||
* <p>Implementations of this contract may be registered directly with the |
||||
* {@code RequestMappingHandlerAdapter} or more likely annotated with |
||||
* {@code @ControllerAdvice} in which case they are auto-detected. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.2 |
||||
*/ |
||||
public interface RequestBodyAdvice { |
||||
|
||||
/** |
||||
* Invoked first to determine if this interceptor applies. |
||||
* @param methodParameter the method parameter |
||||
* @param targetType the target type, not necessarily the same as the method |
||||
* parameter type, e.g. for {@code HttpEntity<String>}. |
||||
* @param converterType the selected converter type |
||||
* @return whether this interceptor should be invoked or not |
||||
*/ |
||||
boolean supports(MethodParameter methodParameter, Type targetType, |
||||
Class<? extends HttpMessageConverter<?>> converterType); |
||||
|
||||
/** |
||||
* Invoked second (and last) if the body is empty. |
||||
* @param body set to {@code null} before the first advice is called |
||||
* @param inputMessage the request |
||||
* @param parameter the method parameter |
||||
* @param targetType the target type, not necessarily the same as the method |
||||
* parameter type, e.g. for {@code HttpEntity<String>}. |
||||
* @param converterType the selected converter type |
||||
* @return the value to use or {@code null} which may then raise an |
||||
* {@code HttpMessageNotReadableException} if the argument is required. |
||||
*/ |
||||
Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, |
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType); |
||||
|
||||
/** |
||||
* Invoked second before the request body is read and converted. |
||||
* @param inputMessage the request |
||||
* @param parameter the target method parameter |
||||
* @param targetType the target type, not necessarily the same as the method |
||||
* parameter type, e.g. for {@code HttpEntity<String>}. |
||||
* @param converterType the converter used to deserialize the body |
||||
* @return the input request or a new instance, never {@code null} |
||||
*/ |
||||
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, |
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType); |
||||
|
||||
/** |
||||
* Invoked third (and last) after the request body is converted to an Object. |
||||
* @param body set to the converter Object before the 1st advice is called |
||||
* @param inputMessage the request |
||||
* @param parameter the target method parameter |
||||
* @param targetType the target type, not necessarily the same as the method |
||||
* parameter type, e.g. for {@code HttpEntity<String>}. |
||||
* @param converterType the converter used to deserialize the body |
||||
* @return the same body or a new instance |
||||
*/ |
||||
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, |
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType); |
||||
|
||||
} |
||||
@ -0,0 +1,176 @@
@@ -0,0 +1,176 @@
|
||||
/* |
||||
* Copyright 2002-2015 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.web.servlet.mvc.method.annotation; |
||||
|
||||
import java.lang.reflect.Type; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.http.HttpInputMessage; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.http.server.ServerHttpRequest; |
||||
import org.springframework.http.server.ServerHttpResponse; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.web.method.ControllerAdviceBean; |
||||
|
||||
|
||||
/** |
||||
* Invokes {@link RequestBodyAdvice} and {@link ResponseBodyAdvice} where each |
||||
* instance may be (and is most likely) wrapped with |
||||
* {@link org.springframework.web.method.ControllerAdviceBean ControllerAdviceBean}. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.2 |
||||
*/ |
||||
class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice<Object> { |
||||
|
||||
private final List<Object> requestBodyAdvice = new ArrayList<Object>(4); |
||||
|
||||
private final List<Object> responseBodyAdvice = new ArrayList<Object>(4); |
||||
|
||||
|
||||
/** |
||||
* Create an instance from a list of objects that are either of type |
||||
* {@code ControllerAdviceBean} or {@code RequestBodyAdvice}. |
||||
*/ |
||||
public RequestResponseBodyAdviceChain(List<Object> requestResponseBodyAdvice) { |
||||
initAdvice(requestResponseBodyAdvice); |
||||
} |
||||
|
||||
private void initAdvice(List<Object> requestResponseBodyAdvice) { |
||||
if (requestResponseBodyAdvice == null) { |
||||
return; |
||||
} |
||||
for (Object advice : requestResponseBodyAdvice) { |
||||
Class<?> beanType = (advice instanceof ControllerAdviceBean ? |
||||
((ControllerAdviceBean) advice).getBeanType() : advice.getClass()); |
||||
if (RequestBodyAdvice.class.isAssignableFrom(beanType)) { |
||||
this.requestBodyAdvice.add(advice); |
||||
} |
||||
else if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { |
||||
this.responseBodyAdvice.add(advice); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private List<Object> getAdvice(Class<?> adviceType) { |
||||
if (RequestBodyAdvice.class.equals(adviceType)) { |
||||
return this.requestBodyAdvice; |
||||
} |
||||
else if (ResponseBodyAdvice.class.equals(adviceType)) { |
||||
return this.responseBodyAdvice; |
||||
} |
||||
else { |
||||
throw new IllegalArgumentException("Unexpected adviceType: " + adviceType); |
||||
} |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public boolean supports(MethodParameter param, Type type, Class<? extends HttpMessageConverter<?>> converterType) { |
||||
throw new UnsupportedOperationException("Not implemented"); |
||||
} |
||||
|
||||
@Override |
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||
throw new UnsupportedOperationException("Not implemented"); |
||||
} |
||||
|
||||
@Override |
||||
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, |
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||
|
||||
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) { |
||||
if (advice.supports(parameter, targetType, converterType)) { |
||||
body = advice.handleEmptyBody(body, inputMessage, parameter, targetType, converterType); |
||||
} |
||||
} |
||||
return body; |
||||
} |
||||
|
||||
@Override |
||||
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, |
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||
|
||||
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) { |
||||
if (advice.supports(parameter, targetType, converterType)) { |
||||
request = advice.beforeBodyRead(request, parameter, targetType, converterType); |
||||
} |
||||
} |
||||
return request; |
||||
} |
||||
|
||||
@Override |
||||
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, |
||||
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { |
||||
|
||||
for (RequestBodyAdvice advice : getMatchingAdvice(parameter, RequestBodyAdvice.class)) { |
||||
if (advice.supports(parameter, targetType, converterType)) { |
||||
body = advice.afterBodyRead(body, inputMessage, parameter, targetType, converterType); |
||||
} |
||||
} |
||||
return body; |
||||
} |
||||
|
||||
@Override |
||||
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType contentType, |
||||
Class<? extends HttpMessageConverter<?>> converterType, |
||||
ServerHttpRequest request, ServerHttpResponse response) { |
||||
|
||||
return processBody(body, returnType, contentType, converterType, request, response); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private <T> Object processBody(Object body, MethodParameter returnType, MediaType contentType, |
||||
Class<? extends HttpMessageConverter<?>> converterType, |
||||
ServerHttpRequest request, ServerHttpResponse response) { |
||||
|
||||
for (ResponseBodyAdvice<?> advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) { |
||||
if (advice.supports(returnType, converterType)) { |
||||
body = ((ResponseBodyAdvice<T>) advice).beforeBodyWrite((T) body, returnType, |
||||
contentType, converterType, request, response); |
||||
} |
||||
} |
||||
return body; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private <A> List<A> getMatchingAdvice(MethodParameter parameter, Class<? extends A> adviceType) { |
||||
List<Object> availableAdvice = getAdvice(adviceType); |
||||
if (CollectionUtils.isEmpty(availableAdvice)) { |
||||
return Collections.emptyList(); |
||||
} |
||||
List<A> result = new ArrayList<A>(availableAdvice.size()); |
||||
for (Object advice : availableAdvice) { |
||||
if (advice instanceof ControllerAdviceBean) { |
||||
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice; |
||||
if (!adviceBean.isApplicableToBeanType(parameter.getContainingClass())) { |
||||
continue; |
||||
} |
||||
advice = adviceBean.resolveBean(); |
||||
} |
||||
if (adviceType.isAssignableFrom(advice.getClass())) { |
||||
result.add((A) advice); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
} |
||||
@ -1,78 +0,0 @@
@@ -1,78 +0,0 @@
|
||||
/* |
||||
* Copyright 2002-2015 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.web.servlet.mvc.method.annotation; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.springframework.core.MethodParameter; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.http.server.ServerHttpRequest; |
||||
import org.springframework.http.server.ServerHttpResponse; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.web.method.ControllerAdviceBean; |
||||
|
||||
/** |
||||
* Invokes a a list of {@link ResponseBodyAdvice} beans. |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
* @since 4.1 |
||||
*/ |
||||
class ResponseBodyAdviceChain { |
||||
|
||||
private final List<Object> advice; |
||||
|
||||
|
||||
public ResponseBodyAdviceChain(List<Object> advice) { |
||||
this.advice = advice; |
||||
} |
||||
|
||||
|
||||
public boolean hasAdvice() { |
||||
return !CollectionUtils.isEmpty(this.advice); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public <T> T invoke(T body, MethodParameter returnType, |
||||
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, |
||||
ServerHttpRequest request, ServerHttpResponse response) { |
||||
|
||||
if (this.advice != null) { |
||||
for (Object advice : this.advice) { |
||||
if (advice instanceof ControllerAdviceBean) { |
||||
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice; |
||||
if (!adviceBean.isApplicableToBeanType(returnType.getContainingClass())) { |
||||
continue; |
||||
} |
||||
advice = adviceBean.resolveBean(); |
||||
} |
||||
if (advice instanceof ResponseBodyAdvice) { |
||||
ResponseBodyAdvice<T> typedAdvice = (ResponseBodyAdvice<T>) advice; |
||||
if (typedAdvice.supports(returnType, selectedConverterType)) { |
||||
body = typedAdvice.beforeBodyWrite(body, returnType, |
||||
selectedContentType, selectedConverterType, request, response); |
||||
} |
||||
} |
||||
else { |
||||
throw new IllegalStateException("Expected ResponseBodyAdvice: " + advice); |
||||
} |
||||
} |
||||
} |
||||
return body; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue