blob: 9270b0709a3f137e03020dbb4b6c253cfbd0b3b3 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you 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.apache.juneau.rest.client;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.AddFlag.*;
import static org.apache.juneau.httppart.HttpPartType.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.logging.*;
import org.apache.http.*;
import org.apache.http.client.config.*;
import org.apache.http.client.entity.*;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.*;
import org.apache.http.concurrent.*;
import org.apache.http.entity.*;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.params.*;
import org.apache.http.protocol.*;
import org.apache.juneau.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.html.*;
import org.apache.juneau.http.*;
import org.apache.juneau.http.header.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.msgpack.*;
import org.apache.juneau.oapi.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.plaintext.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.uon.*;
import org.apache.juneau.urlencoding.*;
import org.apache.juneau.xml.*;
/**
* Represents a request to a remote REST resource.
*
* <p>
* Instances of this class are created by the various creator methods on the {@link RestClient} class.
*
* <ul class='seealso'>
* <li class='jc'>{@link RestClient}
* <li class='link'>{@doc juneau-rest-client}
* </ul>
*/
public class RestRequest extends BeanSession implements HttpUriRequest, Configurable {
private static final ContentType TEXT_PLAIN = ContentType.create("text/plain");
private final RestClient client; // The client that created this call.
private final HttpRequestBase request; // The request.
private RestResponse response; // The response.
List<RestCallInterceptor> interceptors = new ArrayList<>(); // Used for intercepting and altering requests.
private boolean ignoreErrors;
private Object input;
private boolean hasInput; // input() was called, even if it's setting 'null'.
private Serializer serializer;
private Parser parser;
private HttpPartSerializerSession partSerializer;
private HttpPartSchema requestBodySchema;
private URIBuilder uriBuilder;
private List<NameValuePair> formData;
private Predicate<Integer> errorCodes;
private HttpHost target;
private HttpContext context;
/**
* Constructs a REST call with the specified method name.
*
* @param client The client that created this request.
* @param uri The target URI.
* @param method The HTTP method name (uppercase).
* @param hasBody Whether this method has a body.
* @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
*/
protected RestRequest(RestClient client, URI uri, String method, boolean hasBody) throws RestCallException {
super(client, BeanSessionArgs.DEFAULT);
this.client = client;
this.request = createInnerRequest(method, uri, hasBody);
this.errorCodes = client.errorCodes;
this.partSerializer = client.getPartSerializerSession();
this.uriBuilder = new URIBuilder(request.getURI());
this.ignoreErrors = client.ignoreErrors;
}
/**
* Constructs the {@link HttpRequestBase} object that ends up being passed to the client execute method.
*
* <p>
* Subclasses can override this method to create their own request base objects.
*
* @param method The HTTP method.
* @param uri The HTTP URI.
* @param hasBody Whether the HTTP request has a body.
* @return A new {@link HttpRequestBase} object.
*/
protected HttpRequestBase createInnerRequest(String method, URI uri, boolean hasBody) {
HttpRequestBase req = hasBody ? new BasicHttpEntityRequestBase(this, method) : new BasicHttpRequestBase(this, method);
req.setURI(uri);
return req;
}
//------------------------------------------------------------------------------------------------------------------
// Configuration
//------------------------------------------------------------------------------------------------------------------
/**
* Convenience method for specifying JSON as the marshalling transmission media type for this request only.
*
* <p>
* {@link JsonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link JsonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"application/json"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"application/json"</js> unless overridden
* {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(JsonSerializer.<jk>class</jk>).parser(JsonParser.<jk>class</jk>)</c>.
*
* @return This object (for method chaining).
*/
public RestRequest json() {
return serializer(JsonSerializer.class).parser(JsonParser.class);
}
/**
* Convenience method for specifying Simplified JSON as the marshalling transmission media type for this request only.
*
* <p>
* Simplified JSON is typically useful for automated tests because you can do simple string comparison of results
* without having to escape lots of quotes.
*
* <p>
* {@link SimpleJsonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link JsonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"application/json"</js> unless overridden
* by {@link #header(String,Object)} or {@link #accept(Object)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"application/json+simple"</js> unless overridden
* by {@link #header(String,Object)} or {@link #contentType(Object)}, or per-request via {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Can be combined with other marshaller setters such as {@link #xml()} to provide support for multiple languages.
* <ul>
* <li>When multiple languages are supported, the <c>Accept</c> and <c>Content-Type</c> headers control which marshallers are used, or uses the
* last-enabled language if the headers are not set.
* </ul>
* <p>
* Identical to calling <c>serializer(SimpleJsonSerializer.<jk>class</jk>).parser(JsonParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses Simplified JSON marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().simpleJson().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest simpleJson() {
return serializer(SimpleJsonSerializer.class).parser(SimpleJsonParser.class);
}
/**
* Convenience method for specifying XML as the marshalling transmission media type for this request only.
*
* <p>
* {@link XmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link XmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"text/xml"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"text/xml"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(XmlSerializer.<jk>class</jk>).parser(XmlParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses XML marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().xml().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest xml() {
return serializer(XmlSerializer.class).parser(XmlParser.class);
}
/**
* Convenience method for specifying HTML as the marshalling transmission media type for this request only.
*
* <p>
* POJOs are converted to HTML without any sort of doc wrappers.
*
* <p>
* {@link HtmlSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(HtmlSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses HTML marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().html().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest html() {
return serializer(HtmlSerializer.class).parser(HtmlParser.class);
}
/**
* Convenience method for specifying HTML DOC as the marshalling transmission media type for this request only.
*
* <p>
* POJOs are converted to fully renderable HTML pages.
*
* <p>
* {@link HtmlDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"text/html"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"text/html"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(HtmlDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses HTML Doc marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().htmlDoc().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest htmlDoc() {
return serializer(HtmlDocSerializer.class).parser(HtmlParser.class);
}
/**
* Convenience method for specifying Stripped HTML DOC as the marshalling transmission media type for this request only.
*
* <p>
* Same as {@link #htmlDoc()} but without the header and body tags and page title and description.
*
* <p>
* {@link HtmlStrippedDocSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link HtmlParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"text/html+stripped"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"text/html+stripped"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(HtmlStrippedDocSerializer.<jk>class</jk>).parser(HtmlParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses HTML Stripped Doc marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().htmlStrippedDoc().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest htmlStrippedDoc() {
return serializer(HtmlStrippedDocSerializer.class).parser(HtmlParser.class);
}
/**
* Convenience method for specifying Plain Text as the marshalling transmission media type for this request only.
*
* <p>
* Plain text marshalling typically only works on simple POJOs that can be converted to and from strings using
* swaps, swap methods, etc...
*
* <p>
* {@link PlainTextSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link PlainTextParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"text/plain"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"text/plain"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(PlainTextSerializer.<jk>class</jk>).parser(PlainTextParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses Plain Text marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().plainText().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest plainText() {
return serializer(PlainTextSerializer.class).parser(PlainTextParser.class);
}
/**
* Convenience method for specifying MessagePack as the marshalling transmission media type for this request only.
*
* <p>
* MessagePack is a binary equivalent to JSON that takes up considerably less space than JSON.
*
* <p>
* {@link MsgPackSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link MsgPackParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"octal/msgpack"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"octal/msgpack"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(MsgPackSerializer.<jk>class</jk>).parser(MsgPackParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses MessagePack marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().msgPack().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest msgPack() {
return serializer(MsgPackSerializer.class).parser(MsgPackParser.class);
}
/**
* Convenience method for specifying UON as the marshalling transmission media type for this request only.
*
* <p>
* UON is Url-Encoding Object notation that is equivalent to JSON but suitable for transmission as URL-encoded
* query and form post values.
*
* <p>
* {@link UonSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* {@link UonParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"text/uon"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"text/uon"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(UonSerializer.<jk>class</jk>).parser(UonParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses UON marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().uon().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest uon() {
return serializer(UonSerializer.class).parser(UonParser.class);
}
/**
* Convenience method for specifying URL-Encoding as the marshalling transmission media type for this request only.
*
* <p>
* {@link UrlEncodingSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* <li>This serializer is NOT used when using the {@link RestRequest#formData(String, Object)} (and related) methods for constructing
* the request body. Instead, the part serializer specified via {@link RestClientBuilder#partSerializer(Class)} is used.
* </ul>
* <p>
* {@link UrlEncodingParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"application/x-www-form-urlencoded"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(UrlEncodingSerializer.<jk>class</jk>).parser(UrlEncodingParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses URL-Encoded marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().urlEnc().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest urlEnc() {
return serializer(UrlEncodingSerializer.class).parser(UrlEncodingParser.class);
}
/**
* Convenience method for specifying OpenAPI as the marshalling transmission media type for this request only.
*
* <p>
* OpenAPI is a language that allows serialization to formats that use {@link HttpPartSchema} objects to describe their structure.
*
* <p>
* {@link OpenApiSerializer} will be used to serialize POJOs to request bodies unless overridden per request via {@link RestRequest#serializer(Serializer)}.
* <ul>
* <li>The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* <li>Typically the {@link RestRequest#body(Object, HttpPartSchema)} method will be used to specify the body of the request with the
* schema describing it's structure.
* </ul>
* <p>
* {@link OpenApiParser} will be used to parse POJOs from response bodies unless overridden per request via {@link RestRequest#parser(Parser)}.
* <ul>
* <li>The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
* <li>Typically the {@link RestResponseBody#schema(HttpPartSchema)} method will be used to specify the structure of the response body.
* </ul>
* <p>
* <c>Accept</c> request header will be set to <js>"text/openapi"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#accept(Object)}.
* <p>
* <c>Content-Type</c> request header will be set to <js>"text/openapi"</js> unless overridden
* by {@link RestRequest#header(String,Object)} or {@link RestRequest#contentType(Object)}.
* <p>
* Identical to calling <c>serializer(OpenApiSerializer.<jk>class</jk>).parser(OpenApiParser.<jk>class</jk>)</c>.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Construct a client that uses OpenAPI marshalling.</jc>
* RestClient <jv>client</jv> = RestClient.<jsm>create</jsm>().openApi().build();
* </p>
*
* @return This object (for method chaining).
*/
public RestRequest openApi() {
return serializer(OpenApiSerializer.class).parser(OpenApiParser.class);
}
/**
* Specifies the serializer to use on the request body.
*
* <p>
* Overrides the serializers specified on the {@link RestClient}.
*
* <p>
* The serializer is not modified by an of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
*
* <p>
* If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer.
*
* @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
* @return This object (for method chaining).
*/
public RestRequest serializer(Serializer serializer) {
this.serializer = serializer;
return this;
}
/**
* Specifies the serializer to use on the request body.
*
* <p>
* Overrides the serializers specified on the {@link RestClient}.
*
* <p>
* The serializer can be configured using any of the serializer property setters (e.g. {@link RestClientBuilder#sortCollections()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
*
* <p>
* If the <c>Content-Type</c> header is not set on the request, it will be set to the media type of this serializer.
*
* @param serializer The serializer used to serialize POJOs to the body of the HTTP request.
* @return This object (for method chaining).
*/
public RestRequest serializer(Class<? extends Serializer> serializer) {
this.serializer = client.getInstance(serializer);
return this;
}
/**
* Specifies the parser to use on the response body.
*
* <p>
* Overrides the parsers specified on the {@link RestClient}.
*
* <p>
* The parser is not modified by any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
*
* <p>
* If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser.
*
* @param parser The parser used to parse POJOs from the body of the HTTP response.
* @return This object (for method chaining).
*/
public RestRequest parser(Parser parser) {
this.parser = parser;
return this;
}
/**
* Specifies the parser to use on the response body.
*
* <p>
* Overrides the parsers specified on the {@link RestClient}.
*
* <p>
* The parser can be configured using any of the parser property setters (e.g. {@link RestClientBuilder#strict()}),
* bean context property setters (e.g. {@link RestClientBuilder#swaps(Object...)}), or generic property setters (e.g. {@link RestClientBuilder#set(String, Object)}) defined on this builder class.
*
* <p>
* If the <c>Accept</c> header is not set on the request, it will be set to the media type of this parser.
*
* @param parser The parser used to parse POJOs from the body of the HTTP response.
* @return This object (for method chaining).
*/
public RestRequest parser(Class<? extends Parser> parser) {
this.parser = client.getInstance(parser);
return this;
}
/**
* Allows you to override what status codes are considered error codes that would result in a {@link RestCallException}.
*
* <p>
* The default error code predicate is: <code>x -&gt; x &gt;= 400</code>.
*
* @param value The new predicate for calculating error codes.
* @return This object (for method chaining).
*/
public RestRequest errorCodes(Predicate<Integer> value) {
this.errorCodes = value;
return this;
}
/**
* Add one or more interceptors for this call only.
*
* @param interceptors The interceptors to add to this call.
* @return This object (for method chaining).
* @throws RestCallException If init method on interceptor threw an exception.
*/
public RestRequest interceptors(RestCallInterceptor...interceptors) throws RestCallException {
try {
for (RestCallInterceptor i : interceptors) {
this.interceptors.add(i);
i.onInit(this);
}
} catch (RuntimeException | RestCallException e) {
throw e;
} catch (Exception e) {
throw new RestCallException(null, e, "Interceptor threw an exception on init.");
}
return this;
}
/**
* Prevent {@link RestCallException RestCallExceptions} from being thrown when HTTP status 400+ is encountered.
*
* <p>
* This overrides the <l>ignoreErrors</l> property on the client.
*
* @return This object (for method chaining).
*/
public RestRequest ignoreErrors() {
this.ignoreErrors = true;
return this;
}
/**
* Sets <c>Debug: value</c> header on this request.
*
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest debug() throws RestCallException {
header("Debug", true);
return this;
}
/**
* Returns <jk>true</jk> if debug mode is currently enabled.
*/
@Override
public boolean isDebug() {
return getHeader("Debug", "false").equalsIgnoreCase("true");
}
/**
* Specifies the target host for the request.
*
* @param target The target host for the request.
* Implementations may accept <jk>null</jk> if they can still determine a route, for example to a default
* target or by inspecting the request.
* @return This object (for method chaining).
*/
public RestRequest target(HttpHost target) {
this.target = target;
return this;
}
/**
* Override the context to use for the execution.
*
* @param context The context to use for the execution, or <jk>null</jk> to use the default context.
* @return This object (for method chaining).
*/
public RestRequest context(HttpContext context) {
this.context = context;
return this;
}
//------------------------------------------------------------------------------------------------------------------
// URI
//------------------------------------------------------------------------------------------------------------------
/**
* Sets the URI for this request.
*
* <p>
* Can be any of the following types:
* <ul>
* <li>{@link URI}
* <li>{@link URL}
* <li>{@link URIBuilder}
* <li>Anything else converted to a string using {@link Object#toString()}.
* </ul>
*
* <p>
* Relative URI strings will be interpreted as relative to the root URI defined on the client.
*
* @param uri
* The URI of the remote REST resource.
* <br>This overrides the URI passed in from the client.
* <br>Can be any of the following types:
* <ul>
* <li>{@link URIBuilder}
* <li>{@link URI}
* <li>{@link URL}
* <li>{@link String}
* <li>{@link Object} - Converted to <c>String</c> using <c>toString()</c>
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid URI syntax detected.
*/
public RestRequest uri(Object uri) throws RestCallException {
URI x = client.toURI(uri, null);
if (x.getScheme() != null)
uriBuilder.setScheme(x.getScheme());
if (x.getHost() != null)
uriBuilder.setHost(x.getHost());
if (x.getPort() != -1)
uriBuilder.setPort(x.getPort());
if (x.getUserInfo() != null)
uriBuilder.setUserInfo(x.getUserInfo());
if (x.getFragment() != null)
uriBuilder.setFragment(x.getFragment());
if (x.getQuery() != null)
uriBuilder.setCustomQuery(x.getQuery());
uriBuilder.setPath(x.getPath());
return this;
}
/**
* Sets the URI scheme.
*
* @param scheme The new URI host.
* @return This object (for method chaining).
*/
public RestRequest scheme(String scheme) {
uriBuilder.setScheme(scheme);
return this;
}
/**
* Sets the URI host.
*
* @param host The new URI host.
* @return This object (for method chaining).
*/
public RestRequest host(String host) {
uriBuilder.setHost(host);
return this;
}
/**
* Sets the URI port.
*
* @param port The new URI port.
* @return This object (for method chaining).
*/
public RestRequest port(int port) {
uriBuilder.setPort(port);
return this;
}
/**
* Sets the URI user info.
*
* @param userInfo The new URI user info.
* @return This object (for method chaining).
*/
public RestRequest userInfo(String userInfo) {
uriBuilder.setUserInfo(userInfo);
return this;
}
/**
* Sets the URI user info.
*
* @param username The new URI username.
* @param password The new URI password.
* @return This object (for method chaining).
*/
public RestRequest userInfo(String username, String password) {
uriBuilder.setUserInfo(username, password);
return this;
}
/**
* Sets the URI fragment.
*
* @param fragment The URI fragment. The value is expected to be unescaped and may contain non ASCII characters.
* @return This object (for method chaining).
*/
public RestRequest fragment(String fragment) {
uriBuilder.setFragment(fragment);
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Path
//------------------------------------------------------------------------------------------------------------------
/**
* Replaces a path parameter of the form <js>"{name}"</js> in the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Sets path to "/bar".</jc>
* <jv>client</jv>
* .get(<js>"/{foo}"</js>)
* .path(<js>"foo"</js>, <js>"bar"</js>)
* .run();
* </p>
*
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest path(String name, Object value) throws RestCallException {
return paths(serializedNameValuePair(name, value, PATH, partSerializer, null, null));
}
/**
* Replaces a path parameter of the form <js>"{name}"</js> in the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Sets path to "/bar".</jc>
* <jv>client</jv>
* .get(<js>"/{foo}"</js>)
* .path(BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>))
* .run();
* </p>
*
* @param pair The parameter.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest path(NameValuePair pair) throws RestCallException {
return paths(pair);
}
/**
* Replaces a path parameter of the form <js>"{name}"</js> in the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Sets path to "/bar|baz".</jc>
* <jv>client</jv>
* .get(<js>"/{foo}"</js>)
* .path(
* <js>"foo"</js>, AList.<jsm>of</jsm>(<js>"bar"</js>,<js>"baz"</js>),
* HttpPartSchema.<jsf>T_ARRAY_PIPES</jsf>
* )
* .run();
* </p>
*
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @param schema The part schema. Can be <jk>null</jk>.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest path(String name, Object value, HttpPartSchema schema) throws RestCallException {
return paths(serializedNameValuePair(name, value, PATH, partSerializer, schema, null));
}
/**
* Replaces multiple path parameter of the form <js>"{name}"</js> in the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Sets path to "/baz/qux".</jc>
* <jv>client</jv>
* .get(<js>"/{foo}/{bar}"</js>)
* .paths(
* BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>, <js>"baz"</js>),
* AMap.<jsm>of</jsm>(<js>"bar"</js>, <js>"qux"</js>)
* )
* .run();
* </p>
*
* @param params
* The path parameters to set.
* <br>Can be any of the following types:
* <ul>
* <li>{@link NameValuePair}
* <li>{@link NameValuePairable}
* <li>{@link java.util.Map.Entry}
* <li>{@link NameValuePairSupplier}
* <li>{@link Map}
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
@SuppressWarnings("rawtypes")
public RestRequest paths(Object...params) throws RestCallException {
for (Object o : params) {
if (BasicNameValuePair.canCast(o)) {
innerPath(BasicNameValuePair.cast(o));
} else if (o instanceof NameValuePairSupplier) {
for (NameValuePair p : (NameValuePairSupplier)o)
innerPath(p);
} else if (o instanceof Collection) {
for (Object o2 : (Collection<?>)o)
innerPath(BasicNameValuePair.cast(o2));
} else if (o != null && o.getClass().isArray()) {
for (int i = 0; i < Array.getLength(o); i++)
innerPath(BasicNameValuePair.cast(Array.get(o, i)));
} else if (o instanceof Map) {
for (Map.Entry e : toMap(o).entrySet())
innerPath(serializedNameValuePair(e.getKey(), e.getValue(), PATH, partSerializer, null, null));
} else if (isBean(o)) {
for (Map.Entry<String,Object> e : toBeanMap(o).entrySet())
innerPath(serializedNameValuePair(e.getKey(), e.getValue(), PATH, partSerializer, null, null));
} else if (o != null) {
throw new RestCallException(null, null, "Invalid type passed to paths(): {0}", className(o));
}
}
return this;
}
/**
* Replaces path parameters of the form <js>"{name}"</js> in the URI using free-form key/value pairs.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Sets path to "/baz/qux".</jc>
* <jv>client</jv>
* .get(<js>"/{foo}/{bar}"</js>)
* .pathPairs(
* <js>"foo"</js>,<js>"baz"</js>,
* <js>"bar"</js>,<js>"qux"</js>
* )
* .run();
* </p>
*
* @param pairs The path key/value pairs.
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest pathPairs(Object...pairs) throws RestCallException {
if (pairs.length % 2 != 0)
throw new RestCallException(null, null, "Odd number of parameters passed into pathPairs()");
for (int i = 0; i < pairs.length; i+=2)
paths(serializedNameValuePair(pairs[i], pairs[i+1], PATH, partSerializer, null, null));
return this;
}
RestRequest pathArg(String name, Object value, HttpPartSchema schema, HttpPartSerializerSession serializer) throws RestCallException {
boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairSupplier || isNameValuePairArray(value);
if (! isMulti)
return innerPath(serializedNameValuePair(name, value, PATH, serializer, schema, null));
if (BasicNameValuePair.canCast(value)) {
innerPath(BasicNameValuePair.cast(value));
} else if (value instanceof NameValuePairSupplier) {
for (Object o : (NameValuePairSupplier)value)
innerPath(BasicNameValuePair.cast(o));
} else if (value instanceof Collection) {
for (Object o : (Collection<?>)value)
innerPath(BasicNameValuePair.cast(o));
} else if (value != null && value.getClass().isArray()) {
for (int i = 0; i < Array.getLength(value); i++)
innerPath(BasicNameValuePair.cast(Array.get(value, i)));
} else if (value instanceof Map) {
for (Map.Entry<Object,Object> p : toMap(value).entrySet())
innerPath(serializedNameValuePair(p.getKey(), p.getValue(), PATH, serializer, schema, null));
} else if (isBean(value)) {
for (Map.Entry<String,Object> p : toBeanMap(value).entrySet())
innerPath(serializedNameValuePair(p.getKey(), p.getValue(), PATH, serializer, schema, null));
} else if (value != null) {
throw new RestCallException(null, null, "Invalid value type for path arg ''{0}'': {1}", name, className(value));
}
return this;
}
private RestRequest innerPath(NameValuePair param) throws RestCallException {
String path = uriBuilder.getPath();
String name = param.getName(), value = param.getValue();
String var = "{" + name + "}";
if (path.indexOf(var) == -1 && ! name.equals("/*"))
throw new RestCallException(null, null, "Path variable {"+name+"} was not found in path.");
String p = null;
if (name.equals("/*"))
p = path.replaceAll("\\/\\*$", "/" + value);
else
p = path.replace(var, String.valueOf(value));
uriBuilder.setPath(p);
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Query
//------------------------------------------------------------------------------------------------------------------
/**
* Sets a query parameter on the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameter "foo=bar|baz".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .query(
* <jsf>APPEND</jsf>,
* <js>"foo"</js>, AList.<jsm>of</jsm>(<js>"bar"</js>,<js>"baz"</js>),
* HttpPartSchema.<jsf>T_ARRAY_PIPES</jsf>
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @param schema The schema object that defines the format of the output.
* <ul>
* <li>If <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
* <li>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest query(AddFlag flag, String name, Object value, HttpPartSchema schema) throws RestCallException {
return queries(flag, serializedNameValuePair(name, value, QUERY, partSerializer, schema, EnumSet.of(flag)));
}
/**
* Adds a query parameter to the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameter "foo=bar".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .query(<js>"foo"</js>, <js>"bar"</js>)
* .run();
* </p>
*
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest query(String name, Object value) throws RestCallException {
return queries(serializedNameValuePair(name, value, QUERY, partSerializer, null, null));
}
/**
* Adds a query parameter to the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameter "foo=bar".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .query(BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>))
* .run();
* </p>
*
* @param pair The parameter.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest query(NameValuePair pair) throws RestCallException {
return queries(pair);
}
/**
* Adds a query parameter to the URI.
*
* <p>
* The optional schema allows for specifying how part should be serialized (as a pipe-delimited list for example).
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Creates query parameter "foo=bar|baz"</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .query(
* <js>"foo"</js>, AList.<jsm>of</jsm>(<js>"bar"</js>,<js>"baz"</js>),
* HttpPartSchema.<jsf>T_ARRAY_PIPES</jsf>
* )
* .run();
* </p>
*
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @param schema The HTTP part schema. Can be <jk>null</jk>.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest query(String name, Object value, HttpPartSchema schema) throws RestCallException {
return queries(serializedNameValuePair(name, value, QUERY, partSerializer, schema, null));
}
/**
* Adds a query parameter to the URI.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameter "foo=bar".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .query(
* <jsf>APPEND</jsf>,
* <js>"foo"</js>, <js>"bar"</js>
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest query(AddFlag flag, String name, Object value) throws RestCallException {
return queries(flag, serializedNameValuePair(name, value, QUERY, partSerializer, null, EnumSet.of(flag)));
}
/**
* Sets multiple parameters on the query string.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameters "foo=bar&baz=qux".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .queries(
* BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>,<js>"bar"</js>),
* AMap.<jsm>of</jsm>(<js>"baz"</js>,<js>"qux"</js>)
* )
* .run();
* </p>
*
* @param params
* The parameters to set.
* <br>Can be any of the following types:
* <ul>
* <li>{@link NameValuePair}
* <li>{@link NameValuePairable}
* <li>{@link java.util.Map.Entry}
* <li>{@link NameValuePairSupplier}
* <li>{@link Map}
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest queries(Object...params) throws RestCallException {
return queries(APPEND, params);
}
/**
* Sets multiple parameters on the query string.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameters "foo=bar&baz=qux".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .queries(
* <jsf>APPEND</jsf>,
* BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>,<js>"bar"</js>),
* AMap.<jsm>of</jsm>(<js>"baz"</js>,<js>"qux"</js>)
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param params
* The parameters to set.
* <br>Can be any of the following types:
* <ul>
* <li>{@link NameValuePair}
* <li>{@link NameValuePairable}
* <li>{@link java.util.Map.Entry}
* <li>{@link NameValuePairSupplier}
* <li>{@link Map}
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest queries(AddFlag flag, Object...params) throws RestCallException {
List<NameValuePair> l = new ArrayList<>();
for (Object o : params) {
if (BasicNameValuePair.canCast(o)) {
l.add(BasicNameValuePair.cast(o));
} else if (o instanceof NameValuePairSupplier) {
for (NameValuePair p : (NameValuePairSupplier)o)
l.add(p);
} else if (o instanceof Collection) {
for (Object o2 : (Collection<?>)o)
l.add(BasicNameValuePair.cast(o2));
} else if (o != null && o.getClass().isArray()) {
for (int i = 0; i < Array.getLength(o); i++)
l.add(BasicNameValuePair.cast(Array.get(o, i)));
} else if (o instanceof Map) {
for (Map.Entry<Object,Object> e : toMap(o).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), QUERY, partSerializer, null, EnumSet.of(flag)));
} else if (isBean(o)) {
for (Map.Entry<String,Object> e : toBeanMap(o).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), QUERY, partSerializer, null, EnumSet.of(flag)));
} else if (o != null) {
throw new RestCallException(null, null, "Invalid type passed to queries(): {0}", className(o));
}
}
return innerQuery(EnumSet.of(flag), l);
}
/**
* Adds query parameters to the URI query using free-form key/value pairs..
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameters "foo=bar&baz=qux".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .queryPairs(<js>"foo"</js>,<js>"bar"</js>,<js>"baz"</js>,<js>"qux"</js>)
* .run();
* </p>
*
* @param pairs The query key/value pairs.
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest queryPairs(Object...pairs) throws RestCallException {
if (pairs.length % 2 != 0)
throw new RestCallException(null, null, "Odd number of parameters passed into queryPairs()");
for (int i = 0; i < pairs.length; i+=2)
queries(serializedNameValuePair(pairs[i], pairs[i+1], QUERY, partSerializer, null, null));
return this;
}
/**
* Adds a free-form custom query.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds query parameter "foo=bar&baz=qux".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .queryCustom(<js>"foo=bar&baz=qux"</js>)
* .run();
* </p>
*
* @param value The parameter value.
* <br>Can be any of the following types:
* <ul>
* <li>
* {@link CharSequence}
* <li>
* {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
* <li>
* {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
* <li>
* {@link NameValuePairSupplier} - Converted to a URL-encoded query.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest queryCustom(Object value) throws RestCallException {
try {
String q = null;
if (value instanceof Reader)
q = IOUtils.read((Reader)value);
else if (value instanceof InputStream)
q = IOUtils.read((InputStream)value);
else
q = stringify(value); // Works for NameValuePairs.
uriBuilder.setCustomQuery(q);
} catch (IOException e) {
throw new RestCallException(null, e, "Could not read custom query.");
}
return this;
}
RestRequest queryArg(EnumSet<AddFlag> flags, String name, Object value, HttpPartSchema schema, HttpPartSerializerSession serializer) throws RestCallException {
flags = AddFlag.orDefault(flags);
boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairSupplier || isNameValuePairArray(value);
if (! isMulti)
return innerQuery(flags, AList.of(serializedNameValuePair(name, value, QUERY, serializer, schema, flags)));
List<NameValuePair> l = AList.create();
if (BasicNameValuePair.canCast(value)) {
l.add(BasicNameValuePair.cast(value));
} else if (value instanceof NameValuePairSupplier) {
for (Object o : (NameValuePairSupplier)value)
l.add(BasicNameValuePair.cast(o));
} else if (value instanceof Collection) {
for (Object o : (Collection<?>)value)
l.add(BasicNameValuePair.cast(o));
} else if (value != null && value.getClass().isArray()) {
for (int i = 0; i < Array.getLength(value); i++)
l.add(BasicNameValuePair.cast(Array.get(value, i)));
} else if (value instanceof Map) {
for (Map.Entry<Object,Object> e : toMap(value).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), QUERY, serializer, schema, flags));
} else if (isBean(value)) {
for (Map.Entry<String,Object> e : toBeanMap(value).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), QUERY, serializer, schema, flags));
} else {
return queryCustom(value);
}
return innerQuery(flags, l);
}
private RestRequest innerQuery(EnumSet<AddFlag> flags, List<NameValuePair> params) {
flags = AddFlag.orDefault(flags);
params.removeIf(x -> x.getValue() == null);
if (flags.contains(SKIP_IF_EMPTY))
params.removeIf(x -> isEmpty(x.getValue()));
if (flags.contains(REPLACE)) {
List<NameValuePair> l = uriBuilder.getQueryParams();
for (NameValuePair p : params)
for (Iterator<NameValuePair> i = l.iterator(); i.hasNext();)
if (i.next().getName().equals(p.getName()))
i.remove();
l.addAll(params);
uriBuilder.setParameters(l);
} else if (flags.contains(PREPEND)) {
List<NameValuePair> l = uriBuilder.getQueryParams();
l.addAll(0, params);
uriBuilder.setParameters(l);
} else {
uriBuilder.addParameters(params);
}
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Form data
//------------------------------------------------------------------------------------------------------------------
/**
* Adds a form-data parameter to the request body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds form data parameter "foo=bar|baz".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formData(
* <jsf>APPEND</jsf>,
* <js>"foo"</js>, AList.<jsm>of</jsm>(<js>"bar"</js>,<js>"baz"</js>),
* HttpPartSchema.<jsf>T_ARRAY_PIPES</jsf>
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @param schema The schema object that defines the format of the output.
* <ul>
* <li>If <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
* <li>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formData(AddFlag flag, String name, Object value, HttpPartSchema schema) throws RestCallException {
return formDatas(flag, serializedNameValuePair(name, value, FORMDATA, partSerializer, schema, EnumSet.of(flag)));
}
/**
* Adds a form-data parameter to the request body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds form data parameter "foo=bar|baz".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formData(<js>"foo"</js>, <js>"bar"</js>)
* .run();
* </p>
*
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formData(String name, Object value) throws RestCallException {
return formDatas(serializedNameValuePair(name, value, FORMDATA, partSerializer, null, null));
}
/**
* Adds a form-data parameter to the request body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds form data parameter "foo=bar".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formData(BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>, <js>"bar"</js>))
* .run();
* </p>
*
* @param pair The parameter.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formData(NameValuePair pair) throws RestCallException {
return formDatas(pair);
}
/**
* Adds a form-data parameter to the request body.
*
* <p>
* The optional schema allows for specifying how part should be serialized (as a pipe-delimited list for example).
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds form data parameter "foo=bar|baz".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formData(
* <js>"foo"</js>, AList.<jsm>of</jsm>(<js>"bar"</js>,<js>"baz"</js>),
* HttpPartSchema.<jsf>T_ARRAY_PIPES</jsf>
* )
* .run();
* </p>
*
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @param schema The HTTP part schema. Can be <jk>null</jk>.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formData(String name, Object value, HttpPartSchema schema) throws RestCallException {
return formDatas(serializedNameValuePair(name, value, FORMDATA, partSerializer, schema, null));
}
/**
* Adds a form-data parameter to the request body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds form data parameter "foo=bar".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formData(
* <jsf>APPEND</jsf>,
* <js>"foo"</js>, <js>"bar"</js>
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param name The parameter name.
* @param value The parameter value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formData(AddFlag flag, String name, Object value) throws RestCallException {
return formDatas(flag, serializedNameValuePair(name, value, FORMDATA, partSerializer, null, EnumSet.of(flag)));
}
/**
* Adds a form-data parameter to the request body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds form data parameters "foo=bar&baz=qux".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formDatas(
* BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>,<js>"bar"</js>),
* AMap.<jsm>of</jsm>(<js>"baz"</js>,<js>"qux"</js>)
* )
* .run();
* </p>
*
* @param params
* The parameters to set.
* <br>Can be any of the following types:
* <ul>
* <li>{@link NameValuePair}
* <li>{@link NameValuePairable}
* <li>{@link java.util.Map.Entry}
* <li>{@link NameValuePairSupplier}
* <li>{@link Map}
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formDatas(Object...params) throws RestCallException {
return formDatas(APPEND, params);
}
/**
* Adds multiple form-data parameters to the request body.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds form data parameters "foo=bar&baz=qux".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formDatas(
* <jsf>APPEND</jsf>,
* BasicNameValuePair.<jsm>of</jsm>(<js>"foo"</js>,<js>"bar"</js>),
* AMap.<jsm>of</jsm>(<js>"baz"</js>,<js>"qux"</js>)
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param params
* The parameters to set.
* <br>Can be any of the following types:
* <ul>
* <li>{@link NameValuePair}
* <li>{@link NameValuePairable}
* <li>{@link java.util.Map.Entry}
* <li>{@link NameValuePairSupplier}
* <li>{@link Map}
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formDatas(AddFlag flag, Object...params) throws RestCallException {
List<NameValuePair> l = new ArrayList<>();
for (Object o : params) {
if (BasicNameValuePair.canCast(o)) {
l.add(BasicNameValuePair.cast(o));
} else if (o instanceof NameValuePairSupplier) {
for (NameValuePair p : (NameValuePairSupplier)o)
l.add(p);
} else if (o instanceof Collection) {
for (Object o2 : (Collection<?>)o)
l.add(BasicNameValuePair.cast(o2));
} else if (o != null && o.getClass().isArray()) {
for (int i = 0; i < Array.getLength(o); i++)
l.add(BasicNameValuePair.cast(Array.get(o, i)));
} else if (o instanceof Map) {
for (Map.Entry<Object,Object> e : toMap(o).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), FORMDATA, partSerializer, null, EnumSet.of(flag)));
} else if (isBean(o)) {
for (Map.Entry<String,Object> e : toBeanMap(o).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), FORMDATA, partSerializer, null, EnumSet.of(flag)));
} else if (o != null) {
throw new RestCallException(null, null, "Invalid type passed to formDatas(): {0}", className(o));
}
}
return innerFormData(EnumSet.of(flag), l);
}
/**
* Adds form-data parameters to the request body using free-form key/value pairs.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Creates form data "key1=val1&key2=val2".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formDataPairs(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>)
* .run();
* </p>
*
* @param pairs The form-data key/value pairs.
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formDataPairs(Object...pairs) throws RestCallException {
if (pairs.length % 2 != 0)
throw new RestCallException(null, null, "Odd number of parameters passed into formDataPairs()");
for (int i = 0; i < pairs.length; i+=2)
formDatas(serializedNameValuePair(pairs[i], pairs[i+1], FORMDATA, partSerializer, null, null));
return this;
}
/**
* Adds form-data parameters as the entire body of the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Creates form data "foo=bar&baz=qux".</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formDataCustom(<js>"foo=bar&baz=qux"</js>)
* .run();
*
* <jc>// Creates form data "foo=bar&baz=qux" using StringEntity.</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .formDataCustom(<jk>new</jk> StringEntity(<js>"foo=bar&baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>))
* .run();
*
* <jc>// Creates form data "foo=bar&baz=qux" using StringEntity and body().</jc>
* <jv>client</jv>
* .formPost(<jsf>URI</jsf>)
* .body(<jk>new</jk> StringEntity(<js>"foo=bar&baz=qux"</js>,<js>"application/x-www-form-urlencoded"</js>))
* .run();
* </p>
*
* @param value The parameter value.
* <br>Can be any of the following types:
* <ul class='spaced-list'>
* <li>
* {@link CharSequence}
* <li>
* {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
* <li>
* {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
* <li>
* {@link HttpResource}/{@link BasicHttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request.
* <li>
* {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
* <li>
* {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the
* {@link RestClient}.
* <li>
* {@link NameValuePairSupplier} - Converted to a URL-encoded FORM post.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest formDataCustom(Object value) throws RestCallException {
contentType("application/x-www-form-urlencoded");
body(value instanceof CharSequence ? new StringReader(value.toString()) : value);
return this;
}
RestRequest formDataArg(EnumSet<AddFlag> flags, String name, Object value, HttpPartSchema schema, HttpPartSerializerSession serializer) throws RestCallException {
flags = AddFlag.orDefault(flags);
boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof NameValuePairSupplier || isNameValuePairArray(value);
if (! isMulti)
return innerFormData(flags, AList.of(serializedNameValuePair(name, value, FORMDATA, serializer, schema, flags)));
List<NameValuePair> l = AList.create();
if (BasicNameValuePair.canCast(value)) {
l.add(BasicNameValuePair.cast(value));
} else if (value instanceof NameValuePairSupplier) {
for (Object o : (NameValuePairSupplier)value)
l.add(BasicNameValuePair.cast(o));
} else if (value instanceof Collection) {
for (Object o : (Collection<?>)value)
l.add(BasicNameValuePair.cast(o));
} else if (value != null && value.getClass().isArray()) {
for (int i = 0; i < Array.getLength(value); i++)
l.add(BasicNameValuePair.cast(Array.get(value, i)));
} else if (value instanceof Map) {
for (Map.Entry<Object,Object> e : toMap(value).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), FORMDATA, serializer, schema, flags));
} else if (isBean(value)) {
for (Map.Entry<String,Object> e : toBeanMap(value).entrySet())
l.add(serializedNameValuePair(e.getKey(), e.getValue(), FORMDATA, serializer, schema, flags));
} else {
return formDataCustom(value);
}
return innerFormData(flags, l);
}
private RestRequest innerFormData(EnumSet<AddFlag> flags, List<NameValuePair> params) {
input = null;
flags = AddFlag.orDefault(flags);
params.removeIf(x -> x.getValue() == null);
if (flags.contains(SKIP_IF_EMPTY))
params.removeIf(x -> isEmpty(x.getValue()));
if (formData == null)
formData = new ArrayList<>();
if (flags.contains(REPLACE)) {
for (NameValuePair p : params)
for (Iterator<NameValuePair> i = formData.iterator(); i.hasNext();)
if (i.next().getName().equals(p.getName()))
i.remove();
formData.addAll(params);
} else if (flags.contains(PREPEND)) {
formData.addAll(0, params);
} else {
formData.addAll(params);
}
return this;
}
//------------------------------------------------------------------------------------------------------------------
// Request body
//------------------------------------------------------------------------------------------------------------------
/**
* Sets the body of this request.
*
* @param input
* The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests.
* <br>Can be of the following types:
* <ul class='spaced-list'>
* <li>
* {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
* <li>
* {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
* <li>
* {@link HttpResource}/{@link BasicHttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request.
* <li>
* {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
* <li>
* {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the
* {@link RestClient}.
* <li>
* {@link NameValuePairSupplier} - Converted to a URL-encoded FORM post.
* <li>
* A {@link Supplier} of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException If a retry was attempted, but the entity was not repeatable.
*/
public RestRequest body(Object input) throws RestCallException {
this.input = input;
this.hasInput = true;
this.formData = null;
return this;
}
/**
* Sets the body of this request as straight text bypassing the serializer.
*
* <p class='bcode w800'>
* <jv>client</jv>
* .put(<js>"/foo"</js>)
* .body(<jk>new</jk> StringReader(<js>"foo"</js>))
* .contentType(""
* .run();
*
* <jv>client</jv>
* .put(<js>"/foo"</js>)
* .bodyString(<js>"foo"</js>)
* .run();
* </p>
*
* <p>
* Note that this is different than the following which will serialize <l>foo</l> as a JSON string <l>"foo"</l>.
* <p class='bcode w800'>
* <jv>client</jv>
* .put(<js>"/foo"</js>)
* .json()
* .body(<js>"foo"</js>)
* .run();
* </p>
*
* @param input
* The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests.
* @return This object (for method chaining).
* @throws RestCallException If a retry was attempted, but the entity was not repeatable.
*/
public RestRequest bodyString(Object input) throws RestCallException {
return body(input == null ? null : new StringReader(stringify(input)));
}
/**
* Sets the body of this request.
*
* @param input
* The input to be sent to the REST resource (only valid for PUT/POST/PATCH) requests.
* <br>Can be of the following types:
* <ul class='spaced-list'>
* <li>
* {@link Reader} - Raw contents of {@code Reader} will be serialized to remote resource.
* <li>
* {@link InputStream} - Raw contents of {@code InputStream} will be serialized to remote resource.
* <li>
* {@link HttpResource}/{@link BasicHttpResource} - Raw contents will be serialized to remote resource. Additional headers and media type will be set on request.
* <li>
* {@link HttpEntity}/{@link BasicHttpEntity} - Bypass Juneau serialization and pass HttpEntity directly to HttpClient.
* <li>
* {@link Object} - POJO to be converted to text using the {@link Serializer} registered with the
* {@link RestClient}.
* <li>
* {@link NameValuePairSupplier} - Converted to a URL-encoded FORM post.
* <li>
* A {@link Supplier} of anything on this list.
* </ul>
* @param schema The schema object that defines the format of the output.
* <ul>
* <li>If <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
* <li>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
* </ul>
* @return This object (for method chaining).
* @throws RestCallException If a retry was attempted, but the entity was not repeatable.
*/
public RestRequest body(Object input, HttpPartSchema schema) throws RestCallException {
this.input = input;
this.hasInput = true;
this.formData = null;
this.requestBodySchema = schema;
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// Headers
//-----------------------------------------------------------------------------------------------------------------
/**
* Adds a header on the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds header "Foo: bar|baz".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .header(
* <jsf>APPEND</jsf>,
* <js>"Foo"</js>, AList.<jsm>of</jsm>(<js>"bar"</js>,<js>"baz"</js>)),
* HttpPartSchema.<jsf>T_ARRAY_PIPES</jsf>
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param name The header name.
* @param value The header value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @param schema The schema object that defines the format of the output.
* <ul>
* <li>If <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
* <li>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest header(AddFlag flag, String name, Object value, HttpPartSchema schema) throws RestCallException {
return headers(flag, serializedHeader(name, value, partSerializer, schema, EnumSet.of(flag)));
}
/**
* Appends a header on the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds header "Foo: bar".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .header(<js>"Foo"</js>, <js>"bar"</js>)
* .run();
* </p>
*
* @param name The header name.
* @param value The header value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest header(String name, Object value) throws RestCallException {
return headers(serializedHeader(name, value, partSerializer, null, null));
}
/**
* Appends a header on the request.
*
* <p>
* The optional schema allows for specifying how part should be serialized (as a pipe-delimited list for example).
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds header "Foo: bar|baz".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .header(
* <js>"Foo"</js>, AList.<jsm>of</jsm>(<js>"bar"</js>,<js>"baz"</js>),
* HttpPartSchema.<jsf>T_ARRAY_PIPES</jsf>
* )
* .run();
* </p>
*
* @param name The header name.
* @param value The header value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @param schema The HTTP part schema. Can be <jk>null</jk>.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest header(String name, Object value, HttpPartSchema schema) throws RestCallException {
return headers(serializedHeader(name, value, partSerializer, schema, null));
}
/**
* Adds a header to the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds header "Foo: bar".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .header(
* <jsf>APPEND</jsf>,
* <js>"Foo"</js>, <js>"bar"</js>
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param name The header name.
* @param value The header value.
* <ul>
* <li>Value can be any POJO or POJO {@link Supplier}.
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest header(AddFlag flag, String name, Object value) throws RestCallException {
return headers(flag, serializedHeader(name, value, partSerializer, null, EnumSet.of(flag)));
}
/**
* Appends a header on the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds header "Foo: bar".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .header(BasicHeader.<jsm>of</jsm>(<js>"Foo"</js>, <js>"bar"</js>))
* .run();
* </p>
*
* @param header The header to set.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest header(Header header) throws RestCallException {
return headers(header);
}
/**
* Appends multiple headers to the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds headers "Foo: bar" and "Baz: qux".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .headers(
* BasicHeader.<jsm>of</jsm>(<js>"Foo"</js>, <js>"bar"</js>),
* AMap.<jsm>of</jsm>(<js>"Baz"</js>, <js>"qux"</js>)
* )
* .run();
* </p>
*
* @param headers
* The headers to set.
* <br>Can be any of the following types:
* <ul>
* <li>{@link Header} (including any subclasses such as {@link Accept})
* <li>{@link Headerable}
* <li>{@link java.util.Map.Entry}
* <li>{@link HeaderSupplier}
* <li>{@link Map}
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest headers(Object...headers) throws RestCallException {
return headers(APPEND, headers);
}
/**
* Sets multiple headers on the request.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds headers "Foo: bar" and "Baz: qux".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .headers(
* <jsf>APPEND</jsf>,
* BasicHeader.<jsm>of</jsm>(<js>"Foo"</js>, <js>"bar"</js>),
* AMap.<jsm>of</jsm>(<js>"Baz"</js>, <js>"qux"</js>)
* )
* .run();
* </p>
*
* @param flag How to add this parameter.
* <ul>
* <li>{@link AddFlag#APPEND APPEND} (default) - Append to end.
* <li>{@link AddFlag#PREPEND PREPEND} - Prepend to beginning.
* <li>{@link AddFlag#REPLACE REPLACE} - Delete any existing with same name and append to end.
* </ul>
* @param headers
* The headers to set.
* <br>Can be any of the following types:
* <ul>
* <li>{@link Header} (including any subclasses such as {@link Accept})
* <li>{@link Headerable}
* <li>{@link java.util.Map.Entry}
* <li>{@link HeaderSupplier}
* <li>{@link Map}
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest headers(AddFlag flag, Object...headers) throws RestCallException {
List<Header> l = new ArrayList<>();
for (Object o : headers) {
if (BasicHeader.canCast(o)) {
l.add(BasicHeader.cast(o));
} else if (o instanceof HeaderSupplier) {
for (Header h : (HeaderSupplier)o)
l.add(h);
} else if (o instanceof Collection) {
for (Object o2 : (Collection<?>)o)
l.add(BasicHeader.cast(o2));
} else if (o != null && o.getClass().isArray()) {
for (int i = 0; i < Array.getLength(o); i++)
l.add(BasicHeader.cast(Array.get(o, i)));
} else if (o instanceof Map) {
for (Map.Entry<Object,Object> e : toMap(o).entrySet())
l.add(serializedHeader(e.getKey(), e.getValue(), partSerializer, null, EnumSet.of(flag)));
} else if (isBean(o)) {
for (Map.Entry<String,Object> e : toBeanMap(o).entrySet())
l.add(serializedHeader(e.getKey(), e.getValue(), partSerializer, null, EnumSet.of(flag)));
} else if (o != null) {
throw new RestCallException(null, null, "Invalid type passed to headers(): {0}", className(o));
}
}
return innerHeaders(EnumSet.of(flag), l);
}
/**
* Appends multiple headers on the request using freeform key/value pairs.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Adds headers "Foo: bar" and "Baz: qux".</jc>
* <jv>client</jv>
* .get(<jsf>URI</jsf>)
* .headers(<js>"Foo"</js>,<js>"bar"</js>,<js>"Baz"</js>,<js>"qux"</js>)
* .run();
* </p>
*
* @param pairs The form-data key/value pairs.
* <ul>
* <li>Values can be any POJO.
* <li>Values converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest headerPairs(Object...pairs) throws RestCallException {
List<Header> l = new ArrayList<>();
if (pairs.length % 2 != 0)
throw new RestCallException(null, null, "Odd number of parameters passed into headerPairs()");
for (int i = 0; i < pairs.length; i+=2)
l.add(serializedHeader(pairs[i], pairs[i+1], partSerializer, null, null));
return innerHeaders(EnumSet.of(APPEND), l);
}
RestRequest headerArg(EnumSet<AddFlag> flags, String name, Object value, HttpPartSchema schema, HttpPartSerializerSession serializer) throws RestCallException {
flags = AddFlag.orDefault(flags);
boolean isMulti = isEmpty(name) || "*".equals(name) || value instanceof HeaderSupplier || isHeaderArray(value);
if (! isMulti)
return innerHeaders(flags, AList.of(serializedHeader(name, value, serializer, schema, flags)));
List<Header> l = AList.create();
if (BasicHeader.canCast(value)) {
l.add(BasicHeader.cast(value));
} else if (value instanceof HeaderSupplier) {
for (Object o : (HeaderSupplier)value)
l.add(BasicHeader.cast(o));
} else if (value instanceof Collection) {
for (Object o : (Collection<?>)value)
l.add(BasicHeader.cast(o));
} else if (value != null && value.getClass().isArray()) {
for (int i = 0; i < Array.getLength(value); i++)
l.add(BasicHeader.cast(Array.get(value, i)));
} else if (value instanceof Map) {
for (Map.Entry<Object,Object> e : toMap(value).entrySet())
l.add(serializedHeader(e.getKey(), e.getValue(), serializer, schema, flags));
} else if (isBean(value)) {
for (Map.Entry<String,Object> e : toBeanMap(value).entrySet())
l.add(serializedHeader(e.getKey(), e.getValue(), serializer, schema, flags));
} else if (value != null) {
throw new RestCallException(null, null, "Invalid value type for header arg ''{0}'': {1}", name, className(value));
}
return innerHeaders(flags, l);
}
private RestRequest innerHeaders(EnumSet<AddFlag> flags, Collection<Header> headers) {
flags = AddFlag.orDefault(flags);
headers.removeIf(x -> x.getValue() == null);
if (flags.contains(SKIP_IF_EMPTY))
headers.removeIf(x -> isEmpty(x.getValue()));
if (flags.contains(REPLACE)) {
for (Header h : headers)
removeHeaders(h.getName());
} else if (flags.contains(PREPEND)) {
for (Header h : AList.of(headers)) {
for (Header h2 : getHeaders(h.getName()))
headers.add(h2);
removeHeaders(h.getName());
}
}
for (Header h : headers) {
addHeader(h);
}
return this;
}
/**
* Convenience method for reading the current last value of the specified header.
*
* @param name The header name.
* @return The header value or <jk>null</jk> if not set.
*/
public String getHeader(String name) {
return getHeader(name, null);
}
/**
* Convenience method for reading the current last value of the specified header.
*
* @param name The header name.
* @param def The default value if the header is not set.
* @return The header value or the default value if not set.
*/
public String getHeader(String name, String def) {
Header h = getLastHeader(name);
return h == null ? def : h.getValue();
}
//------------------------------------------------------------------------------------------------------------------
// Specialized headers.
//------------------------------------------------------------------------------------------------------------------
/**
* Sets the value for the <c>Accept</c> request header.
*
* <p>
* This overrides the media type specified on the parser, but is overridden by calling
* <code>header(<js>"Accept"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest accept(Object value) throws RestCallException {
return header("Accept", value);
}
/**
* Sets the value for the <c>Accept-Charset</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Accept-Charset"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest acceptCharset(Object value) throws RestCallException {
return header("Accept-Charset", value);
}
/**
* Sets the value for the <c>Accept-Encoding</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Accept-Encoding"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest acceptEncoding(Object value) throws RestCallException {
return header("Accept-Encoding", value);
}
/**
* Sets the value for the <c>Accept-Language</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Accept-Language"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest acceptLanguage(Object value) throws RestCallException {
return header("Accept-Language", value);
}
/**
* Sets the value for the <c>Authorization</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Authorization"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest authorization(Object value) throws RestCallException {
return header("Authorization", value);
}
/**
* Sets the value for the <c>Cache-Control</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Cache-Control"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest cacheControl(Object value) throws RestCallException {
return header("Cache-Control", value);
}
/**
* Sets the value for the <c>Connection</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Connection"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest connection(Object value) throws RestCallException {
return header("Connection", value);
}
/**
* Sets the value for the <c>Content-Length</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Content-Length"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest contentLength(Object value) throws RestCallException {
return header("Content-Length", value);
}
/**
* Sets the value for the <c>Content-Type</c> request header.
*
* <p>
* This overrides the media type specified on the serializer, but is overridden by calling
* <code>header(<js>"Content-Type"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest contentType(Object value) throws RestCallException {
return header("Content-Type", value);
}
/**
* Shortcut for setting the <c>Accept</c> and <c>Content-Type</c> headers on a request.
*
* @param value The new header values.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest mediaType(Object value) throws RestCallException {
return header("Accept", value).header("Content-Type", value);
}
/**
* Sets the value for the <c>Content-Encoding</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Content-Encoding"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest contentEncoding(Object value) throws RestCallException {
return header("Content-Encoding", value);
}
/**
* Sets the value for the <c>Date</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Date"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest date(Object value) throws RestCallException {
return header("Date", value);
}
/**
* Sets the value for the <c>Expect</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Expect"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest expect(Object value) throws RestCallException {
return header("Expect", value);
}
/**
* Sets the value for the <c>Forwarded</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Forwarded"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest forwarded(Object value) throws RestCallException {
return header("Forwarded", value);
}
/**
* Sets the value for the <c>From</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"From"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest from(Object value) throws RestCallException {
return header("From", value);
}
/**
* Sets the value for the <c>Host</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Host"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest hostHeader(Object value) throws RestCallException {
return header("Host", value);
}
/**
* Sets the value for the <c>If-Match</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"If-Match"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest ifMatch(Object value) throws RestCallException {
return header("If-Match", value);
}
/**
* Sets the value for the <c>If-Modified-Since</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"If-Modified-Since"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest ifModifiedSince(Object value) throws RestCallException {
return header("If-Modified-Since", value);
}
/**
* Sets the value for the <c>If-None-Match</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"If-None-Match"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest ifNoneMatch(Object value) throws RestCallException {
return header("If-None-Match", value);
}
/**
* Sets the value for the <c>If-Range</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"If-Range"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest ifRange(Object value) throws RestCallException {
return header("If-Range", value);
}
/**
* Sets the value for the <c>If-Unmodified-Since</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"If-Unmodified-Since"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest ifUnmodifiedSince(Object value) throws RestCallException {
return header("If-Unmodified-Since", value);
}
/**
* Sets the value for the <c>Max-Forwards</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Max-Forwards"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest maxForwards(Object value) throws RestCallException {
return header("Max-Forwards", value);
}
/**
* When called, <c>No-Trace: true</c> is added to requests.
*
* <p>
* This gives the opportunity for the servlet to not log errors on invalid requests.
* This is useful for testing purposes when you don't want your log file to show lots of errors that are simply the
* results of testing.
*
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest noTrace() throws RestCallException {
return header("No-Trace", true);
}
/**
* Sets the value for the <c>Origin</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Origin"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest origin(Object value) throws RestCallException {
return header("Origin", value);
}
/**
* Sets the value for the <c>Pragma</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Pragma"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest pragma(Object value) throws RestCallException {
return header("Pragma", value);
}
/**
* Sets the value for the <c>Proxy-Authorization</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Proxy-Authorization"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest proxyAuthorization(Object value) throws RestCallException {
return header("Proxy-Authorization", value);
}
/**
* Sets the value for the <c>Range</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Range"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest range(Object value) throws RestCallException {
return header("Range", value);
}
/**
* Sets the value for the <c>Referer</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Referer"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest referer(Object value) throws RestCallException {
return header("Referer", value);
}
/**
* Sets the value for the <c>TE</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"TE"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest te(Object value) throws RestCallException {
return header("TE", value);
}
/**
* Sets the value for the <c>User-Agent</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"User-Agent"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest userAgent(Object value) throws RestCallException {
return header("User-Agent", value);
}
/**
* Sets the value for the <c>Upgrade</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Upgrade"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest upgrade(Object value) throws RestCallException {
return header("Upgrade", value);
}
/**
* Sets the value for the <c>Via</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Via"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest via(Object value) throws RestCallException {
return header("Via", value);
}
/**
* Sets the value for the <c>Warning</c> request header.
*
* <p>
* This is a shortcut for calling <code>header(<js>"Warning"</js>, value);</code>
*
* @param value The new header value.
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest warning(Object value) throws RestCallException {
return header("Warning", value);
}
/**
* Sets the client version by setting the value for the <js>"X-Client-Version"</js> header.
*
* @param value The version string (e.g. <js>"1.2.3"</js>)
* @return This object (for method chaining).
* @throws RestCallException Invalid input.
*/
public RestRequest clientVersion(Object value) throws RestCallException {
return header("X-Client-Version", value);
}
//------------------------------------------------------------------------------------------------------------------
// Execution methods.
//------------------------------------------------------------------------------------------------------------------
/**
* Runs this request and returns the resulting response object.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jk>try</jk> {
* <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).execute().getResponseStatus();
* <jc>// Succeeded!</jc>
* } <jk>catch</jk> (RestCallException <jv>e</jv>) {
* <jc>// Failed!</jc>
* }
* </p>
*
* <ul class='notes'>
* <li>Calling this method multiple times will return the same original response object.
* <li>You must close the returned object if you do not consume the response or execute a method that consumes
* the response.
* <li>If you are only interested in the response code, use the {@link #complete()} method which will automatically
* consume the response so that you don't need to call {@link InputStream#close()} on the response body.
* </ul>
*
* @return The response object.
* @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
*/
public RestResponse run() throws RestCallException {
if (response != null)
throw new RestCallException(response, null, "run() already called.");
try {
HttpEntityEnclosingRequestBase request2 = request instanceof HttpEntityEnclosingRequestBase ? (HttpEntityEnclosingRequestBase)request : null;
request.setURI(uriBuilder.build());
// Pick the serializer if it hasn't been overridden.
Header h = getLastHeader("Content-Type");
String contentType = h == null ? null : h.getValue();
Serializer serializer = this.serializer;
if (serializer == null)
serializer = client.getMatchingSerializer(contentType);
if (contentType == null && serializer != null)
contentType = serializer.getPrimaryMediaType().toString();
// Pick the parser if it hasn't been overridden.
h = getLastHeader("Accept");
String accept = h == null ? null : h.getValue();
Parser parser = this.parser;
if (parser == null)
parser = client.getMatchingParser(accept);
if (accept == null && parser != null)
setHeader("Accept", parser.getPrimaryMediaType().toString());
if (hasInput || formData != null) {
if (request2 == null)
throw new RestCallException(null, null, "Method does not support content entity. Method={0}, URI={1}", getMethod(), getURI());
Object input2 = input;
if (input2 instanceof Supplier)
input2 = ((Supplier<?>)input).get();
HttpEntity entity = null;
if (formData != null)
entity = new UrlEncodedFormEntity(formData);
else if (input2 instanceof NameValuePairSupplier)
entity = new UrlEncodedFormEntity((NameValuePairSupplier)input2);
else if (input2 instanceof HttpResource) {
HttpResource r = (HttpResource)input2;
headers(r.getHeaders());
entity = (HttpEntity)input2;
}
else if (input2 instanceof HttpEntity)
entity = (HttpEntity)input2;
else if (input2 instanceof Reader)
entity = new StringEntity(IOUtils.read((Reader)input2), getRequestContentType(TEXT_PLAIN));
else if (input2 instanceof InputStream)
entity = new InputStreamEntity((InputStream)input2, getRequestContentType(ContentType.APPLICATION_OCTET_STREAM));
else if (serializer != null)
entity = SerializedHttpEntity.of(input2, serializer).schema(requestBodySchema).contentType(contentType);
else {
if (client.hasSerializers()) {
if (contentType == null)
throw new RestCallException(null, null, "Content-Type not specified on request. Cannot match correct serializer. Use contentType(String) or mediaType(String) to specify transport language.");
throw new RestCallException(null, null, "No matching serializer for media type ''{0}''", contentType);
}
if (input2 == null)
input2 = "";
entity = new StringEntity(BeanContext.DEFAULT.getClassMetaForObject(input2).toString(input2), getRequestContentType(TEXT_PLAIN));
}
request2.setEntity(entity);
}
try {
response = client.createResponse(this, client.run(target, request, context), parser);
} catch (Exception e) {
throw e;
}
if (isDebug() || client.logRequests == DetailLevel.FULL)
response.cacheBody();
for (RestCallInterceptor rci : interceptors)
rci.onConnect(this, response);
client.onConnect(this, response);
String method = getMethod();
int sc = response.getStatusCode();
if (errorCodes.test(sc) && ! ignoreErrors) {
throw new RestCallException(response, null, "HTTP method ''{0}'' call to ''{1}'' caused response code ''{2}, {3}''.\nResponse: \n{4}",
method, getURI(), sc, response.getReasonPhrase(), response.getBody().asAbbreviatedString(1000));
}
} catch (RuntimeException | RestCallException e) {
if (response != null)
response.close();
throw e;
} catch (Exception e) {
if (response != null)
response.close();
throw new RestCallException(response, e, "Call failed.");
}
return this.response;
}
/**
* Same as {@link #run()} but allows you to run the call asynchronously.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* Future&lt;RestResponse&gt; <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).runFuture();
*
* <jc>// Do some other stuff</jc>
*
* <jk>try</jk> {
* String <jv>body</jv> = <jv>future</jv>.get().getBody().asString();
* <jc>// Succeeded!</jc>
* } <jk>catch</jk> (RestCallException <jv>e</jv>) {
* <jc>// Failed!</jc>
* }
* </p>
*
* <ul class='notes'>
* <li>Use the {@link RestClientBuilder#executorService(ExecutorService, boolean)} method to customize the
* executor service used for creating {@link Future Futures}.
* </ul>
*
* @return The HTTP status code.
* @throws RestCallException If the executor service was not defined.
*/
public Future<RestResponse> runFuture() throws RestCallException {
return client.getExecutorService().submit(
new Callable<RestResponse>() {
@Override /* Callable */
public RestResponse call() throws Exception {
return run();
}
}
);
}
/**
* Same as {@link #run()} but immediately calls {@link RestResponse#consume()} to clean up the response.
*
* <p>
* Use this method if you're only interested in the status line of the response and not the response entity.
* Attempts to call any of the methods on the response object that retrieve the body (e.g. {@link RestResponseBody#asReader()}
* will cause a {@link RestCallException} to be thrown.
*
* <ul class='notes'>
* <li>You do not need to execute {@link InputStream#close()} on the response body to consume the response.
* </ul>
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* <jc>// Get the response code.
* // No need to call close() on the RestResponse object.</jc>
* <jk>int</jk> <jv>rc</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).complete().getResponseCode();
* </p>
*
* @return The response object.
* @throws RestCallException If an exception or non-200 response code occurred during the connection attempt.
*/
public RestResponse complete() throws RestCallException {
return run().consume();
}
/**
* Same as {@link #complete()} but allows you to run the call asynchronously.
*
* <h5 class='section'>Example:</h5>
* <p class='bcode w800'>
* Future&lt;RestResponse&gt; <jv>future</jv> = <jv>client</jv>.get(<jsf>URI</jsf>).completeFuture();
*
* <jc>// Do some other stuff</jc>
*
* <jk>int</jk> <jv>rc</jv> = <jv>future</jv>.get().getResponseStatus();
* </p>
*
* <ul class='notes'>
* <li>Use the {@link RestClientBuilder#executorService(ExecutorService, boolean)} method to customize the
* executor service used for creating {@link Future Futures}.
* <li>You do not need to execute {@link InputStream#close()} on the response body to consume the response.
* </ul>
*
* @return The HTTP status code.
* @throws RestCallException If the executor service was not defined.
*/
public Future<RestResponse> completeFuture() throws RestCallException {
return client.getExecutorService().submit(
new Callable<RestResponse>() {
@Override /* Callable */
public RestResponse call() throws Exception {
return complete();
}
}
);
}
/**
* Returns <jk>true</jk> if this request has a body.
*
* @return <jk>true</jk> if this request has a body.
*/
public boolean hasHttpEntity() {
return request instanceof HttpEntityEnclosingRequestBase;
}
/**
* Returns the body of this request.
*
* @return The body of this request, or <jk>null</jk> if it doesn't have a body.
*/
public HttpEntity getHttpEntity() {
return hasHttpEntity() ? ((HttpEntityEnclosingRequestBase)request).getEntity() : null;
}
/**
* Logs a message.
*
* @param level The log level.
* @param t The throwable cause.
* @param msg The message with {@link MessageFormat}-style arguments.
* @param args The arguments.
* @return This object (for method chaining).
*/
public RestRequest log(Level level, Throwable t, String msg, Object...args) {
client.log(level, t, msg, args);
return this;
}
/**
* Logs a message.
*
* @param level The log level.
* @param msg The message with {@link MessageFormat}-style arguments.
* @param args The arguments.
* @return This object (for method chaining).
*/
public RestRequest log(Level level, String msg, Object...args) {
client.log(level, msg, args);
return this;
}
//-----------------------------------------------------------------------------------------------------------------
// HttpRequestBase pass-through methods.
//-----------------------------------------------------------------------------------------------------------------
/**
* Sets the actual request configuration.
*
* @param value The new value.
* @return This object (for method chaining).
*/
public RestRequest config(RequestConfig value) {
request.setConfig(value);
return this;
}
/**
* Sets {@link Cancellable} for the ongoing operation.
*
* @param cancellable The cancellable object.
* @return This object (for method chaining).
*/
public RestRequest cancellable(Cancellable cancellable) {
request.setCancellable(cancellable);
return this;
}
/**
* Sets the protocol version for this request.
*
* @param version The protocol version for this request.
* @return This object (for method chaining).
*/
public RestRequest protocolVersion(ProtocolVersion version) {
request.setProtocolVersion(version);
return this;
}
/**
* Used in combination with {@link #cancellable(Cancellable)}.
*
* @return This object (for method chaining).
*/
public RestRequest completed() {
request.completed();
return this;
}
// -----------------------------------------------------------------------------------------------------------------
// HttpUriRequest pass-through methods.
// -----------------------------------------------------------------------------------------------------------------
/**
* Returns the HTTP method this request uses, such as GET, PUT, POST, or other.
*
* @return The HTTP method this request uses, such as GET, PUT, POST, or other.
*/
@Override /* HttpUriRequest */
public String getMethod() {
return request.getMethod();
}
/**
* Returns the original request URI.
*
* <ul class='notes'>
* <li>URI remains unchanged in the course of request execution and is not updated if the request is redirected to another location.
* </ul>
*
* @return The original request URI.
*/
@Override /* HttpUriRequest */
public URI getURI() {
return request.getURI();
}
/**
* Returns the original request URI as a simple string.
*
* <ul class='notes'>
* <li>URI remains unchanged in the course of request execution and is not updated if the request is redirected to another location.
* </ul>
*
* @return The original request URI.
*/
public String getUri() {
return getURI().toString();
}
/**
* Aborts this http request. Any active execution of this method should return immediately.
*
* If the request has not started, it will abort after the next execution.
* <br>Aborting this request will cause all subsequent executions with this request to fail.
*/
@Override /* HttpUriRequest */
public void abort() throws UnsupportedOperationException {
request.abort();
}
@Override /* HttpUriRequest */
public boolean isAborted() {
return request.isAborted();
}
/**
* Returns the request line of this request.
*
* @return The request line.
*/
@Override /* HttpRequest */
public RequestLine getRequestLine() {
return request.getRequestLine();
}
/**
* Returns the protocol version this message is compatible with.
*
* @return The protocol version.
*/
@Override /* HttpMessage */
public ProtocolVersion getProtocolVersion() {
return request.getProtocolVersion();
}
/**
* Checks if a certain header is present in this message.
*
* Header values are ignored.
*
* @param name The header name to check for.
* @return <jk>true</jk> if at least one header with this name is present.
*/
@Override /* HttpMessage */
public boolean containsHeader(String name) {
return request.containsHeader(name);
}
/**
* Returns all the headers with a specified name of this message.
*
* Header values are ignored.
* <br>Headers are ordered in the sequence they will be sent over a connection.
*
* @param name The name of the headers to return.
* @return The headers whose name property equals name.
*/
@Override /* HttpMessage */
public Header[] getHeaders(String name) {
return request.getHeaders(name);
}
/**
* Returns the first header with a specified name of this message.
*
* Header values are ignored.
* <br>If there is more than one matching header in the message the first element of {@link #getHeaders(String)} is returned.
* <br>If there is no matching header in the message <jk>null</jk> is returned.
*
* @param name The name of the header to return.
* @return The first header whose name property equals name or <jk>null</jk> if no such header could be found.
*/
@Override /* HttpMessage */
public Header getFirstHeader(String name) {
return request.getFirstHeader(name);
}
/**
* Returns the last header with a specified name of this message.
*
* Header values are ignored.
* <br>If there is more than one matching header in the message the last element of {@link #getHeaders(String)} is returned.
* <br>If there is no matching header in the message null is returned.
*
* @param name The name of the header to return.
* @return The last header whose name property equals name or <jk>null</jk> if no such header could be found.
*/
@Override /* HttpMessage */
public Header getLastHeader(String name) {
return request.getLastHeader(name);
}
/**
* Returns all the headers of this message.
*
* Headers are ordered in the sequence they will be sent over a connection.
*
* @return All the headers of this message
*/
@Override /* HttpMessage */
public Header[] getAllHeaders() {
return request.getAllHeaders();
}
/**
* Adds a header to this message.
*
* The header will be appended to the end of the list.
*
* <ul class='notes'>
* <li>{@link #header(Header)} is an equivalent method and the preferred method for fluent-style coding.
* </ul>
*
* @param header The header to append.
*/
@Override /* HttpMessage */
public void addHeader(Header header) {
request.addHeader(header);
}
/**
* Adds a header to this message.
*
* The header will be appended to the end of the list.
*
* <ul class='notes'>
* <li>{@link #header(String,Object)} is an equivalent method and the preferred method for fluent-style coding.
* </ul>
*
* @param name The name of the header.
* @param value The value of the header.
*/
@Override /* HttpMessage */
public void addHeader(String name, String value) {
request.addHeader(name, value);
}
/**
* Overwrites the first header with the same name.
*
* The new header will be appended to the end of the list, if no header with the given name can be found.
*
* @param header The header to set.
*/
@Override /* HttpMessage */
public void setHeader(Header header) {
request.setHeader(header);
}
/**
* Overwrites the first header with the same name.
*
* The new header will be appended to the end of the list, if no header with the given name can be found.
*
* @param name The name of the header.
* @param value The value of the header.
*/
@Override /* HttpMessage */
public void setHeader(String name, String value) {
request.setHeader(name, value);
}
/**
* Overwrites all the headers in the message.
*
* @param headers The array of headers to set.
*/
@Override /* HttpMessage */
public void setHeaders(Header[] headers) {
request.setHeaders(headers);
}
/**
* Removes a header from this message.
*
* @param header The header to remove.
*/
@Override /* HttpMessage */
public void removeHeader(Header header) {
request.removeHeader(header);
}
/**
* Removes all headers with a certain name from this message.
*
* @param name The name of the headers to remove.
*/
@Override /* HttpMessage */
public void removeHeaders(String name) {
request.removeHeaders(name);
}
/**
* Returns an iterator of all the headers.
*
* @return Iterator that returns {@link Header} objects in the sequence they are sent over a connection.
*/
@Override /* HttpMessage */
public HeaderIterator headerIterator() {
return request.headerIterator();
}
/**
* Returns an iterator of the headers with a given name.
*
* @param name the name of the headers over which to iterate, or <jk>null</jk> for all headers.
* @return Iterator that returns {@link Header} objects with the argument name in the sequence they are sent over a connection.
*/
@Override /* HttpMessage */
public HeaderIterator headerIterator(String name) {
return request.headerIterator(name);
}
/**
* Returns the parameters effective for this message as set by {@link #setParams(HttpParams)}.
*
* @return The parameters effective for this message as set by {@link #setParams(HttpParams)}.
* @deprecated Use constructor parameters of configuration API provided by HttpClient.
*/
@Override /* HttpMessage */
@Deprecated
public HttpParams getParams() {
return request.getParams();
}
/**
* Provides parameters to be used for the processing of this message.
*
* @param params The parameters.
* @deprecated Use constructor parameters of configuration API provided by HttpClient.
*/
@Override /* HttpMessage */
@Deprecated
public void setParams(HttpParams params) {
request.setParams(params);
}
/**
* Returns the actual request configuration.
*
* @return The actual request configuration.
*/
@Override /* Configurable */
public RequestConfig getConfig() {
return request.getConfig();
}
// -----------------------------------------------------------------------------------------------------------------
// Utility methods
// -----------------------------------------------------------------------------------------------------------------
private ContentType getRequestContentType(ContentType def) {
Header h = request.getFirstHeader("Content-Type");
if (h != null) {
String s = h.getValue();
if (! isEmpty(s))
return ContentType.create(s);
}
return def;
}
@Override
public OMap getProperties() {
return super.getProperties();
}
@SuppressWarnings("unchecked")
private static Map<Object,Object> toMap(Object o) {
return (Map<Object,Object>)o;
}
private static SerializedNameValuePair serializedNameValuePair(Object key, Object value, HttpPartType type, HttpPartSerializerSession serializer, HttpPartSchema schema, EnumSet<AddFlag> flags) {
return key == null ? null : new SerializedNameValuePair(stringify(key), value, type, serializer, schema, flags == null ? false : flags.contains(SKIP_IF_EMPTY));
}
private static SerializedHeader serializedHeader(Object key, Object value, HttpPartSerializerSession serializer, HttpPartSchema schema, EnumSet<AddFlag> flags) {
return key == null ? null : new SerializedHeader(stringify(key), value, serializer, schema, flags == null ? false : flags.contains(SKIP_IF_EMPTY));
}
private static boolean isNameValuePairArray(Object o) {
if (o == null || ! o.getClass().isArray())
return false;
if (NameValuePair.class.isAssignableFrom(o.getClass().getComponentType()))
return true;
return false;
}
private static boolean isHeaderArray(Object o) {
if (o == null || ! o.getClass().isArray())
return false;
if (Header.class.isAssignableFrom(o.getClass().getComponentType()))
return true;
return false;
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
@Override /* Session */
public OMap toMap() {
return super.toMap()
.a("RestCall", new DefaultFilteringOMap()
.a("client", client)
.a("hasInput", hasInput)
.a("ignoreErrors", ignoreErrors)
.a("interceptors", interceptors)
.a("partSerializer", partSerializer)
.a("requestBodySchema", requestBodySchema)
.a("response", response)
.a("serializer", serializer)
);
}
}