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&lt;String&gt; 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 * &#64;RequestMapping("/handle")
045 * public ResponseEntity&lt;String&gt; handle() {
046 *   URI location = ...;
047 *   HttpHeaders responseHeaders = new HttpHeaders();
048 *   responseHeaders.setLocation(location);
049 *   responseHeaders.set("MyResponseHeader", "MyValue");
050 *   return new ResponseEntity&lt;String&gt;("Hello World", responseHeaders, HttpStatus.CREATED);
051 * }
052 * </pre>
053 *
054 * Or, by using a builder accessible via static methods:
055 * <pre class="code">
056 * &#64;RequestMapping("/handle")
057 * public ResponseEntity&lt;String&gt; 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}