View Javadoc
1   /*
2    * Copyright 2002-2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.servlet.mvc.method.annotation;
18  
19  import java.io.IOException;
20  import java.lang.reflect.ParameterizedType;
21  import java.lang.reflect.Type;
22  import java.util.List;
23  
24  import org.springframework.core.MethodParameter;
25  import org.springframework.core.ResolvableType;
26  import org.springframework.http.HttpEntity;
27  import org.springframework.http.HttpHeaders;
28  import org.springframework.http.RequestEntity;
29  import org.springframework.http.ResponseEntity;
30  import org.springframework.http.converter.HttpMessageConverter;
31  import org.springframework.http.server.ServletServerHttpRequest;
32  import org.springframework.http.server.ServletServerHttpResponse;
33  import org.springframework.util.Assert;
34  import org.springframework.web.HttpMediaTypeNotSupportedException;
35  import org.springframework.web.accept.ContentNegotiationManager;
36  import org.springframework.web.bind.support.WebDataBinderFactory;
37  import org.springframework.web.context.request.NativeWebRequest;
38  import org.springframework.web.method.support.ModelAndViewContainer;
39  
40  /**
41   * Resolves {@link HttpEntity} and {@link RequestEntity} method argument values
42   * and also handles {@link HttpEntity} and {@link ResponseEntity} return values.
43   *
44   * <p>An {@link HttpEntity} return type has a specific purpose. Therefore this
45   * handler should be configured ahead of handlers that support any return
46   * value type annotated with {@code @ModelAttribute} or {@code @ResponseBody}
47   * to ensure they don't take over.
48   *
49   * @author Arjen Poutsma
50   * @author Rossen Stoyanchev
51   * @since 3.1
52   */
53  public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
54  
55  	public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> messageConverters) {
56  		super(messageConverters);
57  	}
58  
59  	public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
60  			ContentNegotiationManager contentNegotiationManager) {
61  
62  		super(messageConverters, contentNegotiationManager);
63  	}
64  
65  	public HttpEntityMethodProcessor(List<HttpMessageConverter<?>> messageConverters,
66  			ContentNegotiationManager contentNegotiationManager, List<Object> responseBodyAdvice) {
67  
68  		super(messageConverters, contentNegotiationManager, responseBodyAdvice);
69  	}
70  
71  
72  	@Override
73  	public boolean supportsParameter(MethodParameter parameter) {
74  		return (HttpEntity.class.equals(parameter.getParameterType()) ||
75  				RequestEntity.class.equals(parameter.getParameterType()));
76  	}
77  
78  	@Override
79  	public boolean supportsReturnType(MethodParameter returnType) {
80  		return (HttpEntity.class.isAssignableFrom(returnType.getParameterType()) &&
81  				!RequestEntity.class.isAssignableFrom(returnType.getParameterType()));
82  	}
83  
84  	@Override
85  	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
86  			NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
87  			throws IOException, HttpMediaTypeNotSupportedException {
88  
89  		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
90  		Type paramType = getHttpEntityType(parameter);
91  
92  		Object body = readWithMessageConverters(webRequest, parameter, paramType);
93  		if (RequestEntity.class.equals(parameter.getParameterType())) {
94  			return new RequestEntity<Object>(body, inputMessage.getHeaders(),
95  					inputMessage.getMethod(), inputMessage.getURI());
96  		}
97  		else {
98  			return new HttpEntity<Object>(body, inputMessage.getHeaders());
99  		}
100 	}
101 
102 	private Type getHttpEntityType(MethodParameter parameter) {
103 		Assert.isAssignable(HttpEntity.class, parameter.getParameterType());
104 		Type parameterType = parameter.getGenericParameterType();
105 		if (parameterType instanceof ParameterizedType) {
106 			ParameterizedType type = (ParameterizedType) parameterType;
107 			if (type.getActualTypeArguments().length != 1) {
108 				throw new IllegalArgumentException("Expected single generic parameter on '" +
109 						parameter.getParameterName() + "' in method " + parameter.getMethod());
110 			}
111 			return type.getActualTypeArguments()[0];
112 		}
113 		else if (parameterType instanceof Class) {
114 			return Object.class;
115 		}
116 		throw new IllegalArgumentException("HttpEntity parameter '" + parameter.getParameterName() +
117 				"' in method " + parameter.getMethod() + " is not parameterized");
118 	}
119 
120 	@Override
121 	public void handleReturnValue(Object returnValue, MethodParameter returnType,
122 			ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
123 
124 		mavContainer.setRequestHandled(true);
125 		if (returnValue == null) {
126 			return;
127 		}
128 
129 		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
130 		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
131 
132 		Assert.isInstanceOf(HttpEntity.class, returnValue);
133 		HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
134 		if (responseEntity instanceof ResponseEntity) {
135 			outputMessage.setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
136 		}
137 
138 		HttpHeaders entityHeaders = responseEntity.getHeaders();
139 		if (!entityHeaders.isEmpty()) {
140 			outputMessage.getHeaders().putAll(entityHeaders);
141 		}
142 
143 		Object body = responseEntity.getBody();
144 
145 		// Try even with null body. ResponseBodyAdvice could get involved.
146 		writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
147 
148 		// Ensure headers are flushed even if no body was written.
149 		outputMessage.getBody();
150 	}
151 
152 	@Override
153 	protected Class<?> getReturnValueType(Object returnValue, MethodParameter returnType) {
154 		if (returnValue != null) {
155 			return returnValue.getClass();
156 		}
157 		else {
158 			Type type = getHttpEntityType(returnType);
159 			return ResolvableType.forMethodParameter(returnType, type).resolve(Object.class);
160 		}
161 	}
162 
163 }