001/*
002 * Copyright 2002-2016 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.lang.reflect.Type;
020import java.net.URI;
021import java.nio.charset.Charset;
022import java.util.Arrays;
023
024import org.springframework.util.MultiValueMap;
025import org.springframework.util.ObjectUtils;
026
027/**
028 * Extension of {@link HttpEntity} that adds a {@linkplain HttpMethod method} and
029 * {@linkplain URI uri}.
030 * Used in {@code RestTemplate} and {@code @Controller} methods.
031 *
032 * <p>In {@code RestTemplate}, this class is used as parameter in
033 * {@link org.springframework.web.client.RestTemplate#exchange(RequestEntity, Class) exchange()}:
034 * <pre class="code">
035 * MyRequest body = ...
036 * RequestEntity&lt;MyRequest&gt; request = RequestEntity.post(new URI(&quot;https://example.com/bar&quot;)).accept(MediaType.APPLICATION_JSON).body(body);
037 * ResponseEntity&lt;MyResponse&gt; response = template.exchange(request, MyResponse.class);
038 * </pre>
039 *
040 * <p>If you would like to provide a URI template with variables, consider using
041 * {@link org.springframework.web.util.UriTemplate}:
042 * <pre class="code">
043 * URI uri = new UriTemplate(&quot;https://example.com/{foo}&quot;).expand(&quot;bar&quot;);
044 * RequestEntity&lt;MyRequest&gt; request = RequestEntity.post(uri).accept(MediaType.APPLICATION_JSON).body(body);
045 * </pre>
046 *
047 * <p>Can also be used in Spring MVC, as a parameter in a @Controller method:
048 * <pre class="code">
049 * &#64;RequestMapping("/handle")
050 * public void handle(RequestEntity&lt;String&gt; request) {
051 *   HttpMethod method = request.getMethod();
052 *   URI url = request.getUrl();
053 *   String body = request.getBody();
054 * }
055 * </pre>
056 *
057 * @author Arjen Poutsma
058 * @author Sebastien Deleuze
059 * @since 4.1
060 * @see #getMethod()
061 * @see #getUrl()
062 */
063public class RequestEntity<T> extends HttpEntity<T> {
064
065        private final HttpMethod method;
066
067        private final URI url;
068
069        private final Type type;
070
071
072        /**
073         * Constructor with method and URL but without body nor headers.
074         * @param method the method
075         * @param url the URL
076         */
077        public RequestEntity(HttpMethod method, URI url) {
078                this(null, null, method, url);
079        }
080
081        /**
082         * Constructor with method, URL and body but without headers.
083         * @param body the body
084         * @param method the method
085         * @param url the URL
086         */
087        public RequestEntity(T body, HttpMethod method, URI url) {
088                this(body, null, method, url, null);
089        }
090
091        /**
092         * Constructor with method, URL, body and type but without headers.
093         * @param body the body
094         * @param method the method
095         * @param url the URL
096         * @param type the type used for generic type resolution
097         * @since 4.3
098         */
099        public RequestEntity(T body, HttpMethod method, URI url, Type type) {
100                this(body, null, method, url, type);
101        }
102
103        /**
104         * Constructor with method, URL and headers but without body.
105         * @param headers the headers
106         * @param method the method
107         * @param url the URL
108         */
109        public RequestEntity(MultiValueMap<String, String> headers, HttpMethod method, URI url) {
110                this(null, headers, method, url, null);
111        }
112
113        /**
114         * Constructor with method, URL, headers and body.
115         * @param body the body
116         * @param headers the headers
117         * @param method the method
118         * @param url the URL
119         */
120        public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url) {
121                this(body, headers, method, url, null);
122        }
123
124        /**
125         * Constructor with method, URL, headers, body and type.
126         * @param body the body
127         * @param headers the headers
128         * @param method the method
129         * @param url the URL
130         * @param type the type used for generic type resolution
131         * @since 4.3
132         */
133        public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url, Type type) {
134                super(body, headers);
135                this.method = method;
136                this.url = url;
137                this.type = type;
138        }
139
140
141        /**
142         * Return the HTTP method of the request.
143         * @return the HTTP method as an {@code HttpMethod} enum value
144         */
145        public HttpMethod getMethod() {
146                return this.method;
147        }
148
149        /**
150         * Return the URL of the request.
151         * @return the URL as a {@code URI}
152         */
153        public URI getUrl() {
154                return this.url;
155        }
156
157        /**
158         * Return the type of the request's body.
159         * @return the request's body type, or {@code null} if not known
160         * @since 4.3
161         */
162        public Type getType() {
163                if (this.type == null) {
164                        T body = getBody();
165                        if (body != null) {
166                                return body.getClass();
167                        }
168                }
169                return this.type;
170        }
171
172
173        @Override
174        public boolean equals(Object other) {
175                if (this == other) {
176                        return true;
177                }
178                if (!super.equals(other)) {
179                        return false;
180                }
181                RequestEntity<?> otherEntity = (RequestEntity<?>) other;
182                return (ObjectUtils.nullSafeEquals(getMethod(), otherEntity.getMethod()) &&
183                                ObjectUtils.nullSafeEquals(getUrl(), otherEntity.getUrl()));
184        }
185
186        @Override
187        public int hashCode() {
188                int hashCode = super.hashCode();
189                hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.method);
190                hashCode = 29 * hashCode + ObjectUtils.nullSafeHashCode(this.url);
191                return hashCode;
192        }
193
194        @Override
195        public String toString() {
196                StringBuilder builder = new StringBuilder("<");
197                builder.append(getMethod());
198                builder.append(' ');
199                builder.append(getUrl());
200                builder.append(',');
201                T body = getBody();
202                HttpHeaders headers = getHeaders();
203                if (body != null) {
204                        builder.append(body);
205                        if (headers != null) {
206                                builder.append(',');
207                        }
208                }
209                if (headers != null) {
210                        builder.append(headers);
211                }
212                builder.append('>');
213                return builder.toString();
214        }
215
216
217        // Static builder methods
218
219        /**
220         * Create a builder with the given method and url.
221         * @param method the HTTP method (GET, POST, etc)
222         * @param url the URL
223         * @return the created builder
224         */
225        public static BodyBuilder method(HttpMethod method, URI url) {
226                return new DefaultBodyBuilder(method, url);
227        }
228
229        /**
230         * Create an HTTP GET builder with the given url.
231         * @param url the URL
232         * @return the created builder
233         */
234        public static HeadersBuilder<?> get(URI url) {
235                return method(HttpMethod.GET, url);
236        }
237
238        /**
239         * Create an HTTP HEAD builder with the given url.
240         * @param url the URL
241         * @return the created builder
242         */
243        public static HeadersBuilder<?> head(URI url) {
244                return method(HttpMethod.HEAD, url);
245        }
246
247        /**
248         * Create an HTTP POST builder with the given url.
249         * @param url the URL
250         * @return the created builder
251         */
252        public static BodyBuilder post(URI url) {
253                return method(HttpMethod.POST, url);
254        }
255
256        /**
257         * Create an HTTP PUT builder with the given url.
258         * @param url the URL
259         * @return the created builder
260         */
261        public static BodyBuilder put(URI url) {
262                return method(HttpMethod.PUT, url);
263        }
264
265        /**
266         * Create an HTTP PATCH builder with the given url.
267         * @param url the URL
268         * @return the created builder
269         */
270        public static BodyBuilder patch(URI url) {
271                return method(HttpMethod.PATCH, url);
272        }
273
274        /**
275         * Create an HTTP DELETE builder with the given url.
276         * @param url the URL
277         * @return the created builder
278         */
279        public static HeadersBuilder<?> delete(URI url) {
280                return method(HttpMethod.DELETE, url);
281        }
282
283        /**
284         * Creates an HTTP OPTIONS builder with the given url.
285         * @param url the URL
286         * @return the created builder
287         */
288        public static HeadersBuilder<?> options(URI url) {
289                return method(HttpMethod.OPTIONS, url);
290        }
291
292
293        /**
294         * Defines a builder that adds headers to the request entity.
295         * @param <B> the builder subclass
296         */
297        public interface HeadersBuilder<B extends HeadersBuilder<B>> {
298
299                /**
300                 * Add the given, single header value under the given name.
301                 * @param headerName  the header name
302                 * @param headerValues the header value(s)
303                 * @return this builder
304                 * @see HttpHeaders#add(String, String)
305                 */
306                B header(String headerName, String... headerValues);
307
308                /**
309                 * Set the list of acceptable {@linkplain MediaType media types}, as
310                 * specified by the {@code Accept} header.
311                 * @param acceptableMediaTypes the acceptable media types
312                 */
313                B accept(MediaType... acceptableMediaTypes);
314
315                /**
316                 * Set the list of acceptable {@linkplain Charset charsets}, as specified
317                 * by the {@code Accept-Charset} header.
318                 * @param acceptableCharsets the acceptable charsets
319                 */
320                B acceptCharset(Charset... acceptableCharsets);
321
322                /**
323                 * Set the value of the {@code If-Modified-Since} header.
324                 * <p>The date should be specified as the number of milliseconds since
325                 * January 1, 1970 GMT.
326                 * @param ifModifiedSince the new value of the header
327                 */
328                B ifModifiedSince(long ifModifiedSince);
329
330                /**
331                 * Set the values of the {@code If-None-Match} header.
332                 * @param ifNoneMatches the new value of the header
333                 */
334                B ifNoneMatch(String... ifNoneMatches);
335
336                /**
337                 * Builds the request entity with no body.
338                 * @return the request entity
339                 * @see BodyBuilder#body(Object)
340                 */
341                RequestEntity<Void> build();
342        }
343
344
345        /**
346         * Defines a builder that adds a body to the response entity.
347         */
348        public interface BodyBuilder extends HeadersBuilder<BodyBuilder> {
349
350                /**
351                 * Set the length of the body in bytes, as specified by the
352                 * {@code Content-Length} header.
353                 * @param contentLength the content length
354                 * @return this builder
355                 * @see HttpHeaders#setContentLength(long)
356                 */
357                BodyBuilder contentLength(long contentLength);
358
359                /**
360                 * Set the {@linkplain MediaType media type} of the body, as specified
361                 * by the {@code Content-Type} header.
362                 * @param contentType the content type
363                 * @return this builder
364                 * @see HttpHeaders#setContentType(MediaType)
365                 */
366                BodyBuilder contentType(MediaType contentType);
367
368                /**
369                 * Set the body of the request entity and build the RequestEntity.
370                 * @param <T> the type of the body
371                 * @param body the body of the request entity
372                 * @return the built request entity
373                 */
374                <T> RequestEntity<T> body(T body);
375
376                /**
377                 * Set the body and type of the request entity and build the RequestEntity.
378                 * @param <T> the type of the body
379                 * @param body the body of the request entity
380                 * @param type the type of the body, useful for generic type resolution
381                 * @return the built request entity
382                 * @since 4.3
383                 */
384                <T> RequestEntity<T> body(T body, Type type);
385        }
386
387
388        private static class DefaultBodyBuilder implements BodyBuilder {
389
390                private final HttpMethod method;
391
392                private final URI url;
393
394                private final HttpHeaders headers = new HttpHeaders();
395
396                public DefaultBodyBuilder(HttpMethod method, URI url) {
397                        this.method = method;
398                        this.url = url;
399                }
400
401                @Override
402                public BodyBuilder header(String headerName, String... headerValues) {
403                        for (String headerValue : headerValues) {
404                                this.headers.add(headerName, headerValue);
405                        }
406                        return this;
407                }
408
409                @Override
410                public BodyBuilder accept(MediaType... acceptableMediaTypes) {
411                        this.headers.setAccept(Arrays.asList(acceptableMediaTypes));
412                        return this;
413                }
414
415                @Override
416                public BodyBuilder acceptCharset(Charset... acceptableCharsets) {
417                        this.headers.setAcceptCharset(Arrays.asList(acceptableCharsets));
418                        return this;
419                }
420
421                @Override
422                public BodyBuilder contentLength(long contentLength) {
423                        this.headers.setContentLength(contentLength);
424                        return this;
425                }
426
427                @Override
428                public BodyBuilder contentType(MediaType contentType) {
429                        this.headers.setContentType(contentType);
430                        return this;
431                }
432
433                @Override
434                public BodyBuilder ifModifiedSince(long ifModifiedSince) {
435                        this.headers.setIfModifiedSince(ifModifiedSince);
436                        return this;
437                }
438
439                @Override
440                public BodyBuilder ifNoneMatch(String... ifNoneMatches) {
441                        this.headers.setIfNoneMatch(Arrays.asList(ifNoneMatches));
442                        return this;
443                }
444
445                @Override
446                public RequestEntity<Void> build() {
447                        return new RequestEntity<Void>(this.headers, this.method, this.url);
448                }
449
450                @Override
451                public <T> RequestEntity<T> body(T body) {
452                        return new RequestEntity<T>(body, this.headers, this.method, this.url);
453                }
454
455                @Override
456                public <T> RequestEntity<T> body(T body, Type type) {
457                        return new RequestEntity<T>(body, this.headers, this.method, this.url, type);
458                }
459        }
460
461}