001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http; 018 019import java.net.URI; 020import java.util.Arrays; 021import java.util.LinkedHashSet; 022import java.util.Set; 023 024import org.springframework.util.Assert; 025import org.springframework.util.MultiValueMap; 026import org.springframework.util.ObjectUtils; 027 028/** 029 * Extension of {@link HttpEntity} that adds a {@link HttpStatus} status code. 030 * Used in {@code RestTemplate} as well {@code @Controller} methods. 031 * 032 * <p>In {@code RestTemplate}, this class is returned by 033 * {@link org.springframework.web.client.RestTemplate#getForEntity getForEntity()} and 034 * {@link org.springframework.web.client.RestTemplate#exchange exchange()}: 035 * <pre class="code"> 036 * ResponseEntity<String> entity = template.getForEntity("https://example.com", String.class); 037 * String body = entity.getBody(); 038 * MediaType contentType = entity.getHeaders().getContentType(); 039 * HttpStatus statusCode = entity.getStatusCode(); 040 * </pre> 041 * 042 * <p>Can also be used in Spring MVC, as the return value from a @Controller method: 043 * <pre class="code"> 044 * @RequestMapping("/handle") 045 * public ResponseEntity<String> handle() { 046 * URI location = ...; 047 * HttpHeaders responseHeaders = new HttpHeaders(); 048 * responseHeaders.setLocation(location); 049 * responseHeaders.set("MyResponseHeader", "MyValue"); 050 * return new ResponseEntity<String>("Hello World", responseHeaders, HttpStatus.CREATED); 051 * } 052 * </pre> 053 * 054 * Or, by using a builder accessible via static methods: 055 * <pre class="code"> 056 * @RequestMapping("/handle") 057 * public ResponseEntity<String> handle() { 058 * URI location = ...; 059 * return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body("Hello World"); 060 * } 061 * </pre> 062 * 063 * @author Arjen Poutsma 064 * @author Brian Clozel 065 * @since 3.0.2 066 * @param <T> the body type 067 * @see #getStatusCode() 068 */ 069public class ResponseEntity<T> extends HttpEntity<T> { 070 071 private final Object status; 072 073 074 /** 075 * Create a new {@code ResponseEntity} with the given status code, and no body nor headers. 076 * @param status the status code 077 */ 078 public ResponseEntity(HttpStatus status) { 079 this(null, null, status); 080 } 081 082 /** 083 * Create a new {@code ResponseEntity} with the given body and status code, and no headers. 084 * @param body the entity body 085 * @param status the status code 086 */ 087 public ResponseEntity(T body, HttpStatus status) { 088 this(body, null, status); 089 } 090 091 /** 092 * Create a new {@code HttpEntity} with the given headers and status code, and no body. 093 * @param headers the entity headers 094 * @param status the status code 095 */ 096 public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus status) { 097 this(null, headers, status); 098 } 099 100 /** 101 * Create a new {@code HttpEntity} with the given body, headers, and status code. 102 * @param body the entity body 103 * @param headers the entity headers 104 * @param status the status code 105 */ 106 public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status) { 107 super(body, headers); 108 Assert.notNull(status, "HttpStatus must not be null"); 109 this.status = status; 110 } 111 112 /** 113 * Create a new {@code HttpEntity} with the given body, headers, and status code. 114 * Just used behind the nested builder API. 115 * @param body the entity body 116 * @param headers the entity headers 117 * @param status the status code (as {@code HttpStatus} or as {@code Integer} value) 118 */ 119 private ResponseEntity(T body, MultiValueMap<String, String> headers, Object status) { 120 super(body, headers); 121 this.status = status; 122 } 123 124 125 /** 126 * Return the HTTP status code of the response. 127 * @return the HTTP status as an HttpStatus enum entry 128 */ 129 public HttpStatus getStatusCode() { 130 if (this.status instanceof HttpStatus) { 131 return (HttpStatus) this.status; 132 } 133 else { 134 return HttpStatus.valueOf((Integer) this.status); 135 } 136 } 137 138 /** 139 * Return the HTTP status code of the response. 140 * @return the HTTP status as an int value 141 * @since 4.3 142 */ 143 public int getStatusCodeValue() { 144 if (this.status instanceof HttpStatus) { 145 return ((HttpStatus) this.status).value(); 146 } 147 else { 148 return (Integer) this.status; 149 } 150 } 151 152 153 @Override 154 public boolean equals(Object other) { 155 if (this == other) { 156 return true; 157 } 158 if (!super.equals(other)) { 159 return false; 160 } 161 ResponseEntity<?> otherEntity = (ResponseEntity<?>) other; 162 return ObjectUtils.nullSafeEquals(this.status, otherEntity.status); 163 } 164 165 @Override 166 public int hashCode() { 167 return (super.hashCode() * 29 + ObjectUtils.nullSafeHashCode(this.status)); 168 } 169 170 @Override 171 public String toString() { 172 StringBuilder builder = new StringBuilder("<"); 173 builder.append(this.status.toString()); 174 if (this.status instanceof HttpStatus) { 175 builder.append(' '); 176 builder.append(((HttpStatus) this.status).getReasonPhrase()); 177 } 178 builder.append(','); 179 T body = getBody(); 180 HttpHeaders headers = getHeaders(); 181 if (body != null) { 182 builder.append(body); 183 if (headers != null) { 184 builder.append(','); 185 } 186 } 187 if (headers != null) { 188 builder.append(headers); 189 } 190 builder.append('>'); 191 return builder.toString(); 192 } 193 194 195 // Static builder methods 196 197 /** 198 * Create a builder with the given status. 199 * @param status the response status 200 * @return the created builder 201 * @since 4.1 202 */ 203 public static BodyBuilder status(HttpStatus status) { 204 Assert.notNull(status, "HttpStatus must not be null"); 205 return new DefaultBuilder(status); 206 } 207 208 /** 209 * Create a builder with the given status. 210 * @param status the response status 211 * @return the created builder 212 * @since 4.1 213 */ 214 public static BodyBuilder status(int status) { 215 return new DefaultBuilder(status); 216 } 217 218 /** 219 * Create a builder with the status set to {@linkplain HttpStatus#OK OK}. 220 * @return the created builder 221 * @since 4.1 222 */ 223 public static BodyBuilder ok() { 224 return status(HttpStatus.OK); 225 } 226 227 /** 228 * A shortcut for creating a {@code ResponseEntity} with the given body and 229 * the status set to {@linkplain HttpStatus#OK OK}. 230 * @return the created {@code ResponseEntity} 231 * @since 4.1 232 */ 233 public static <T> ResponseEntity<T> ok(T body) { 234 BodyBuilder builder = ok(); 235 return builder.body(body); 236 } 237 238 /** 239 * Create a new builder with a {@linkplain HttpStatus#CREATED CREATED} status 240 * and a location header set to the given URI. 241 * @param location the location URI 242 * @return the created builder 243 * @since 4.1 244 */ 245 public static BodyBuilder created(URI location) { 246 BodyBuilder builder = status(HttpStatus.CREATED); 247 return builder.location(location); 248 } 249 250 /** 251 * Create a builder with an {@linkplain HttpStatus#ACCEPTED ACCEPTED} status. 252 * @return the created builder 253 * @since 4.1 254 */ 255 public static BodyBuilder accepted() { 256 return status(HttpStatus.ACCEPTED); 257 } 258 259 /** 260 * Create a builder with a {@linkplain HttpStatus#NO_CONTENT NO_CONTENT} status. 261 * @return the created builder 262 * @since 4.1 263 */ 264 public static HeadersBuilder<?> noContent() { 265 return status(HttpStatus.NO_CONTENT); 266 } 267 268 /** 269 * Create a builder with a {@linkplain HttpStatus#BAD_REQUEST BAD_REQUEST} status. 270 * @return the created builder 271 * @since 4.1 272 */ 273 public static BodyBuilder badRequest() { 274 return status(HttpStatus.BAD_REQUEST); 275 } 276 277 /** 278 * Create a builder with a {@linkplain HttpStatus#NOT_FOUND NOT_FOUND} status. 279 * @return the created builder 280 * @since 4.1 281 */ 282 public static HeadersBuilder<?> notFound() { 283 return status(HttpStatus.NOT_FOUND); 284 } 285 286 /** 287 * Create a builder with an 288 * {@linkplain HttpStatus#UNPROCESSABLE_ENTITY UNPROCESSABLE_ENTITY} status. 289 * @return the created builder 290 * @since 4.1.3 291 */ 292 public static BodyBuilder unprocessableEntity() { 293 return status(HttpStatus.UNPROCESSABLE_ENTITY); 294 } 295 296 297 /** 298 * Defines a builder that adds headers to the response entity. 299 * @since 4.1 300 * @param <B> the builder subclass 301 */ 302 public interface HeadersBuilder<B extends HeadersBuilder<B>> { 303 304 /** 305 * Add the given, single header value under the given name. 306 * @param headerName the header name 307 * @param headerValues the header value(s) 308 * @return this builder 309 * @see HttpHeaders#add(String, String) 310 */ 311 B header(String headerName, String... headerValues); 312 313 /** 314 * Copy the given headers into the entity's headers map. 315 * @param headers the existing HttpHeaders to copy from 316 * @return this builder 317 * @since 4.1.2 318 * @see HttpHeaders#add(String, String) 319 */ 320 B headers(HttpHeaders headers); 321 322 /** 323 * Set the set of allowed {@link HttpMethod HTTP methods}, as specified 324 * by the {@code Allow} header. 325 * @param allowedMethods the allowed methods 326 * @return this builder 327 * @see HttpHeaders#setAllow(Set) 328 */ 329 B allow(HttpMethod... allowedMethods); 330 331 /** 332 * Set the entity tag of the body, as specified by the {@code ETag} header. 333 * @param etag the new entity tag 334 * @return this builder 335 * @see HttpHeaders#setETag(String) 336 */ 337 B eTag(String etag); 338 339 /** 340 * Set the time the resource was last changed, as specified by the 341 * {@code Last-Modified} header. 342 * <p>The date should be specified as the number of milliseconds since 343 * January 1, 1970 GMT. 344 * @param lastModified the last modified date 345 * @return this builder 346 * @see HttpHeaders#setLastModified(long) 347 */ 348 B lastModified(long lastModified); 349 350 /** 351 * Set the location of a resource, as specified by the {@code Location} header. 352 * @param location the location 353 * @return this builder 354 * @see HttpHeaders#setLocation(URI) 355 */ 356 B location(URI location); 357 358 /** 359 * Set the caching directives for the resource, as specified by the HTTP 1.1 360 * {@code Cache-Control} header. 361 * <p>A {@code CacheControl} instance can be built like 362 * {@code CacheControl.maxAge(3600).cachePublic().noTransform()}. 363 * @param cacheControl a builder for cache-related HTTP response headers 364 * @return this builder 365 * @since 4.2 366 * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2">RFC-7234 Section 5.2</a> 367 */ 368 B cacheControl(CacheControl cacheControl); 369 370 /** 371 * Configure one or more request header names (e.g. "Accept-Language") to 372 * add to the "Vary" response header to inform clients that the response is 373 * subject to content negotiation and variances based on the value of the 374 * given request headers. The configured request header names are added only 375 * if not already present in the response "Vary" header. 376 * @param requestHeaders request header names 377 * @since 4.3 378 */ 379 B varyBy(String... requestHeaders); 380 381 /** 382 * Build the response entity with no body. 383 * @return the response entity 384 * @see BodyBuilder#body(Object) 385 */ 386 <T> ResponseEntity<T> build(); 387 } 388 389 390 /** 391 * Defines a builder that adds a body to the response entity. 392 * @since 4.1 393 */ 394 public interface BodyBuilder extends HeadersBuilder<BodyBuilder> { 395 396 /** 397 * Set the length of the body in bytes, as specified by the 398 * {@code Content-Length} header. 399 * @param contentLength the content length 400 * @return this builder 401 * @see HttpHeaders#setContentLength(long) 402 */ 403 BodyBuilder contentLength(long contentLength); 404 405 /** 406 * Set the {@linkplain MediaType media type} of the body, as specified by the 407 * {@code Content-Type} header. 408 * @param contentType the content type 409 * @return this builder 410 * @see HttpHeaders#setContentType(MediaType) 411 */ 412 BodyBuilder contentType(MediaType contentType); 413 414 /** 415 * Set the body of the response entity and returns it. 416 * @param <T> the type of the body 417 * @param body the body of the response entity 418 * @return the built response entity 419 */ 420 <T> ResponseEntity<T> body(T body); 421 } 422 423 424 private static class DefaultBuilder implements BodyBuilder { 425 426 private final Object statusCode; 427 428 private final HttpHeaders headers = new HttpHeaders(); 429 430 public DefaultBuilder(Object statusCode) { 431 this.statusCode = statusCode; 432 } 433 434 @Override 435 public BodyBuilder header(String headerName, String... headerValues) { 436 for (String headerValue : headerValues) { 437 this.headers.add(headerName, headerValue); 438 } 439 return this; 440 } 441 442 @Override 443 public BodyBuilder headers(HttpHeaders headers) { 444 if (headers != null) { 445 this.headers.putAll(headers); 446 } 447 return this; 448 } 449 450 @Override 451 public BodyBuilder allow(HttpMethod... allowedMethods) { 452 this.headers.setAllow(new LinkedHashSet<HttpMethod>(Arrays.asList(allowedMethods))); 453 return this; 454 } 455 456 @Override 457 public BodyBuilder contentLength(long contentLength) { 458 this.headers.setContentLength(contentLength); 459 return this; 460 } 461 462 @Override 463 public BodyBuilder contentType(MediaType contentType) { 464 this.headers.setContentType(contentType); 465 return this; 466 } 467 468 @Override 469 public BodyBuilder eTag(String etag) { 470 if (etag != null) { 471 if (!etag.startsWith("\"") && !etag.startsWith("W/\"")) { 472 etag = "\"" + etag; 473 } 474 if (!etag.endsWith("\"")) { 475 etag = etag + "\""; 476 } 477 } 478 this.headers.setETag(etag); 479 return this; 480 } 481 482 @Override 483 public BodyBuilder lastModified(long date) { 484 this.headers.setLastModified(date); 485 return this; 486 } 487 488 @Override 489 public BodyBuilder location(URI location) { 490 this.headers.setLocation(location); 491 return this; 492 } 493 494 @Override 495 public BodyBuilder cacheControl(CacheControl cacheControl) { 496 String ccValue = cacheControl.getHeaderValue(); 497 if (ccValue != null) { 498 this.headers.setCacheControl(ccValue); 499 } 500 return this; 501 } 502 503 @Override 504 public BodyBuilder varyBy(String... requestHeaders) { 505 this.headers.setVary(Arrays.asList(requestHeaders)); 506 return this; 507 } 508 509 @Override 510 public <T> ResponseEntity<T> build() { 511 return body(null); 512 } 513 514 @Override 515 public <T> ResponseEntity<T> body(T body) { 516 return new ResponseEntity<T>(body, this.headers, this.statusCode); 517 } 518 } 519 520}