This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 801c636  RestClient API improvements.
801c636 is described below

commit 801c636e9badfa6c38e15d5590bfb0b6ec0caa00
Author: JamesBognar <[email protected]>
AuthorDate: Wed Jun 16 09:46:32 2021 -0400

    RestClient API improvements.
---
 .../main/java/org/apache/juneau/ContextCache.java  |  20 ++-
 .../org/apache/juneau/rest/client/RestClient.java  | 130 +++++++++++---
 .../juneau/rest/client/RestClientBuilder.java      | 194 ++++++++++++++-------
 .../org/apache/juneau/rest/client/RestRequest.java |  67 +++----
 .../apache/juneau/rest/client/RestClient_Test.java |   2 +-
 5 files changed, 290 insertions(+), 123 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextCache.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextCache.java
index 85e6a6e..1870abe 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextCache.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextCache.java
@@ -40,6 +40,7 @@ public class ContextCache {
 
        private final 
ConcurrentHashMap<Class<?>,ConcurrentHashMap<ContextProperties,Context>> 
contextCache = new ConcurrentHashMap<>();
        private final ConcurrentHashMap<Class<?>,String[]> prefixCache = new 
ConcurrentHashMap<>();
+       private final ConcurrentHashMap<Class<?>,Boolean> cacheableMap = new 
ConcurrentHashMap<>();
 
        // When enabled, this will spit out cache-hit metrics to the console on 
shutdown.
        private static final boolean TRACK_CACHE_HITS = 
Boolean.getBoolean("juneau.trackCacheHits");
@@ -97,6 +98,9 @@ public class ContextCache {
         * @return The
         */
        public <T extends Context> T create(Class<T> c, ContextProperties cp) {
+               if (! isCacheable(c))
+                       return instantiate(c, cp);
+
                String[] prefixes = getPrefixes(c);
 
                if (prefixes == null)
@@ -139,6 +143,18 @@ public class ContextCache {
                return m;
        }
 
+       private boolean isCacheable(Class<?> c) {
+               Boolean b = cacheableMap.get(c);
+               if (b == null) {
+                       b = true;
+                       for (ConfigurableContext c2 : 
ClassInfo.of(c).getAnnotations(ConfigurableContext.class))
+                               if (c2.nocache())
+                                       b = false;
+                       cacheableMap.put(c, b);
+               }
+               return b;
+       }
+
        private String[] getPrefixes(Class<?> c) {
                String[] prefixes = prefixCache.get(c);
                if (prefixes == null) {
@@ -146,10 +162,6 @@ public class ContextCache {
                        for (ClassInfo c2 : 
ClassInfo.of(c).getAllParentsChildFirst()) {
                                ConfigurableContext cc = 
c2.getLastAnnotation(ConfigurableContext.class);
                                if (cc != null) {
-                                       if (cc.nocache()) {
-                                               prefixes = new String[0];
-                                               break;
-                                       }
                                        if (cc.prefixes().length == 0)
                                                ps.add(c2.getSimpleName());
                                        else
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 03aca30..7534011 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -39,6 +39,7 @@ import java.util.logging.*;
 import java.util.regex.*;
 
 import org.apache.http.*;
+import org.apache.http.Header;
 import org.apache.http.client.*;
 import org.apache.http.client.config.*;
 import org.apache.http.client.entity.*;
@@ -54,6 +55,7 @@ import org.apache.juneau.collections.*;
 import org.apache.juneau.cp.*;
 import org.apache.juneau.http.remote.RemoteReturn;
 import org.apache.juneau.http.resource.*;
+import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.http.entity.*;
 import org.apache.juneau.http.header.*;
 import org.apache.juneau.http.part.*;
@@ -1778,24 +1780,109 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
         */
        public static final String RESTCLIENT_serializers = PREFIX + 
"serializers.lo";
 
-       static final String RESTCLIENT_skipEmptyHeaders = PREFIX + 
"skipEmptyHeaders.b";
-       static final String RESTCLIENT_skipEmptyQueryParameter = PREFIX + 
"skipEmptyQueryParameters.b";
-       static final String RESTCLIENT_skipEmptyFormDataParameters = PREFIX + 
"skipEmptyFormDataParameters.b";
+       /**
+        * Configuration property:  Skip empty form data.
+        *
+        * <h5 class='section'>Property:</h5>
+        * <ul class='spaced-list'>
+        *      <li><b>ID:</b>  {@link 
org.apache.juneau.rest.client.RestClient#RESTCLIENT_skipEmptyFormData 
RESTCLIENT_skipEmptyFormData}
+        *      <li><b>Name:</b>  <js>"RestClient.skipEmptyFormData.b"</js>
+        *      <li><b>Data type:</b>  {@link Boolean}
+        *      <li><b>Default:</b>  <jk>false</jk>.
+        *      <li><b>Methods:</b>
+        *              <ul>
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.client.RestClientBuilder#skipEmptyFormData(boolean)}
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.client.RestClientBuilder#skipEmptyFormData()}
+        *              </ul>
+        *      <li><b>Related:</b>
+        *              <ul>
+        *                      <li class='ja'>{@link FormData#skipIfEmpty()}
+        *              </ul>
+        * </ul>
+        *
+        * <h5 class='section'>Description:</h5>
+        * <p>
+        * When enabled, form data consisting of empty strings will be skipped 
on requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link FormData#skipIfEmpty()} annotation overrides this setting.
+        */
+       public static final String RESTCLIENT_skipEmptyFormData = PREFIX + 
"skipEmptyFormData.b";
 
+       /**
+        * Configuration property:  Skip empty header data.
+        *
+        * <h5 class='section'>Property:</h5>
+        * <ul class='spaced-list'>
+        *      <li><b>ID:</b>  {@link 
org.apache.juneau.rest.client.RestClient#RESTCLIENT_skipEmptyHeaders 
RESTCLIENT_skipEmptyHeaders}
+        *      <li><b>Name:</b>  <js>"RestClient.skipEmptyHeaders.b"</js>
+        *      <li><b>Data type:</b>  {@link Boolean}
+        *      <li><b>Default:</b>  <jk>false</jk>.
+        *      <li><b>Methods:</b>
+        *              <ul>
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.client.RestClientBuilder#skipEmptyHeaders(boolean)}
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.client.RestClientBuilder#skipEmptyHeaders()}
+        *              </ul>
+        *      <li><b>Related:</b>
+        *              <ul>
+        *                      <li class='ja'>{@link 
org.apache.juneau.http.annotation.Header#skipIfEmpty()}
+        *              </ul>
+        * </ul>
+        *
+        * <h5 class='section'>Description:</h5>
+        * <p>
+        * When enabled, header values consisting of empty strings will be 
skipped on requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link org.apache.juneau.http.annotation.Header#skipIfEmpty()} 
annotation overrides this setting.
+        */
+       public static final String RESTCLIENT_skipEmptyHeaders = PREFIX + 
"skipEmptyHeaders.b";
+
+       /**
+        * Configuration property:  Skip empty query data.
+        *
+        * <h5 class='section'>Property:</h5>
+        * <ul class='spaced-list'>
+        *      <li><b>ID:</b>  {@link 
org.apache.juneau.rest.client.RestClient#RESTCLIENT_skipEmptyQueryData 
RESTCLIENT_skipEmptyQueryData}
+        *      <li><b>Name:</b>  <js>"RestClient.skipEmptyQueryData.b"</js>
+        *      <li><b>Data type:</b>  {@link Boolean}
+        *      <li><b>Default:</b>  <jk>false</jk>.
+        *      <li><b>Methods:</b>
+        *              <ul>
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.client.RestClientBuilder#skipEmptyQueryData(boolean)}
+        *                      <li class='jm'>{@link 
org.apache.juneau.rest.client.RestClientBuilder#skipEmptyQueryData()}
+        *              </ul>
+        *      <li><b>Related:</b>
+        *              <ul>
+        *                      <li class='ja'>{@link Query#skipIfEmpty()}
+        *              </ul>
+        * </ul>
+        *
+        * <h5 class='section'>Description:</h5>
+        * <p>
+        * When enabled, query parameters consisting of empty strings will be 
skipped on requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link Query#skipIfEmpty()} annotation overrides this setting.
+        */
+       public static final String RESTCLIENT_skipEmptyQueryData = PREFIX + 
"skipEmptyQueryData.b";
 
-       static final String RESTCLIENT_httpClient = PREFIX + "httpClient.o";
-       static final String RESTCLIENT_httpClientBuilder = PREFIX + 
"httpClientBuilder.o";
-       static final String RESTCLIENT_headerDataBuilder = PREFIX + 
"headerDataBuilder.o";
-       static final String RESTCLIENT_formDataBuilder = PREFIX + 
"formDataBuilder.o";
-       static final String RESTCLIENT_queryDataBuilder = PREFIX + 
"queryBuilder.o";
-       static final String RESTCLIENT_pathDataBuilder = PREFIX + 
"pathDataBuilder.o";
+       static final String RESTCLIENT_INTERNAL_httpClient = PREFIX + 
"httpClient.o";
+       static final String RESTCLIENT_INTERNAL_httpClientBuilder = PREFIX + 
"httpClientBuilder.o";
+       static final String RESTCLIENT_INTERNAL_headerDataBuilder = PREFIX + 
"headerDataBuilder.o";
+       static final String RESTCLIENT_INTERNAL_formDataBuilder = PREFIX + 
"formDataBuilder.o";
+       static final String RESTCLIENT_INTERNAL_queryDataBuilder = PREFIX + 
"queryBuilder.o";
+       static final String RESTCLIENT_INTERNAL_pathDataBuilder = PREFIX + 
"pathDataBuilder.o";
 
        final HeaderListBuilder headerData;
        final PartListBuilder queryData, formData, pathData;
        final CloseableHttpClient httpClient;
 
        private final HttpClientConnectionManager connectionManager;
-       private final boolean keepHttpClientOpen, leakDetection;
+       private final boolean keepHttpClientOpen, leakDetection, 
skipEmptyHeaders, skipEmptyQueryData, skipEmptyFormData;
        private final BeanStore beanStore;
        private final UrlEncodingSerializer urlEncodingSerializer;  // Used for 
form posts only.
        final HttpPartSerializer partSerializer;
@@ -1856,11 +1943,14 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
        @SuppressWarnings("unchecked")
        protected RestClient(ContextProperties cp) {
                super(cp);
-               this.httpClient = cp.getInstance(RESTCLIENT_httpClient, 
CloseableHttpClient.class).orElse(null);
-               this.headerData = cp.getInstance(RESTCLIENT_headerDataBuilder, 
HeaderListBuilder.class).orElseGet(HeaderListBuilder::new).copy();
-               this.queryData = cp.getInstance(RESTCLIENT_queryDataBuilder, 
PartListBuilder.class).orElseGet(PartListBuilder::new).copy();
-               this.formData = cp.getInstance(RESTCLIENT_formDataBuilder, 
PartListBuilder.class).orElseGet(PartListBuilder::new).copy();
-               this.pathData = cp.getInstance(RESTCLIENT_pathDataBuilder, 
PartListBuilder.class).orElseGet(PartListBuilder::new).copy();
+               this.httpClient = 
cp.getInstance(RESTCLIENT_INTERNAL_httpClient, 
CloseableHttpClient.class).orElse(null);
+               this.headerData = 
cp.getInstance(RESTCLIENT_INTERNAL_headerDataBuilder, 
HeaderListBuilder.class).orElseGet(HeaderListBuilder::new).copy();
+               this.queryData = 
cp.getInstance(RESTCLIENT_INTERNAL_queryDataBuilder, 
PartListBuilder.class).orElseGet(PartListBuilder::new).copy();
+               this.formData = 
cp.getInstance(RESTCLIENT_INTERNAL_formDataBuilder, 
PartListBuilder.class).orElseGet(PartListBuilder::new).copy();
+               this.pathData = 
cp.getInstance(RESTCLIENT_INTERNAL_pathDataBuilder, 
PartListBuilder.class).orElseGet(PartListBuilder::new).copy();
+               this.skipEmptyHeaders = 
cp.getBoolean(RESTCLIENT_skipEmptyHeaders).orElse(false);
+               this.skipEmptyQueryData = 
cp.getBoolean(RESTCLIENT_skipEmptyQueryData).orElse(false);
+               this.skipEmptyFormData = 
cp.getBoolean(RESTCLIENT_skipEmptyFormData).orElse(false);
 
                BeanStore bs = this.beanStore = new BeanStore()
                        .addBean(ContextProperties.class, cp)
@@ -3236,7 +3326,7 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
         * @return <jk>true</jk> if empty request header values should be 
ignored.
         */
        protected boolean isSkipEmptyHeaders() {
-               return false;
+               return skipEmptyHeaders;
        }
 
        /**
@@ -3244,8 +3334,8 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
         *
         * @return <jk>true</jk> if empty request query parameter values should 
be ignored.
         */
-       protected boolean isSkipEmptyQueryParameters() {
-               return false;
+       protected boolean isSkipEmptyQueryData() {
+               return skipEmptyQueryData;
        }
 
        /**
@@ -3253,8 +3343,8 @@ public class RestClient extends BeanContext implements 
HttpClient, Closeable, Re
         *
         * @return <jk>true</jk> if empty request form-data parameter values 
should be ignored.
         */
-       protected boolean isSkipEmptyFormDataParameters() {
-               return false;
+       protected boolean isSkipEmptyFormData() {
+               return skipEmptyFormData;
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
index b7bf597..ac5f1b2 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
@@ -15,7 +15,6 @@ package org.apache.juneau.rest.client;
 import static org.apache.juneau.parser.InputStreamParser.*;
 import static org.apache.juneau.rest.client.RestClient.*;
 import static org.apache.juneau.BeanTraverseContext.*;
-import static org.apache.juneau.internal.StringUtils.*;
 import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.ExceptionUtils.*;
 import static org.apache.juneau.serializer.OutputStreamSerializer.*;
@@ -37,6 +36,7 @@ import java.util.logging.*;
 import javax.net.ssl.*;
 
 import org.apache.http.*;
+import org.apache.http.Header;
 import org.apache.http.auth.*;
 import org.apache.http.client.*;
 import org.apache.http.client.CookieStore;
@@ -54,6 +54,7 @@ import org.apache.http.protocol.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.html.*;
+import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.http.header.*;
 import org.apache.juneau.http.header.Date;
 import org.apache.juneau.http.part.*;
@@ -110,7 +111,7 @@ public class RestClientBuilder extends BeanContextBuilder {
         */
        protected RestClientBuilder(RestClient copyFrom) {
                super(copyFrom);
-               HttpClientBuilder httpClientBuilder = 
peek(HttpClientBuilder.class, RESTCLIENT_httpClientBuilder);
+               HttpClientBuilder httpClientBuilder = 
peek(HttpClientBuilder.class, RESTCLIENT_INTERNAL_httpClientBuilder);
                this.httpClientBuilder = httpClientBuilder != null ? 
httpClientBuilder : getHttpClientBuilder();
                if (copyFrom == null) {
                        this.headerData = HeaderList.create();
@@ -147,12 +148,12 @@ public class RestClientBuilder extends BeanContextBuilder 
{
        }
 
        private ContextProperties contextProperties() {
-               set(RESTCLIENT_httpClient, getHttpClient());
-               set(RESTCLIENT_httpClientBuilder, getHttpClientBuilder());
-               set(RESTCLIENT_headerDataBuilder, headerData);
-               set(RESTCLIENT_formDataBuilder, formData);
-               set(RESTCLIENT_queryDataBuilder, queryData);
-               set(RESTCLIENT_pathDataBuilder, pathData);
+               set(RESTCLIENT_INTERNAL_httpClient, getHttpClient());
+               set(RESTCLIENT_INTERNAL_httpClientBuilder, 
getHttpClientBuilder());
+               set(RESTCLIENT_INTERNAL_headerDataBuilder, headerData);
+               set(RESTCLIENT_INTERNAL_formDataBuilder, formData);
+               set(RESTCLIENT_INTERNAL_queryDataBuilder, queryData);
+               set(RESTCLIENT_INTERNAL_pathDataBuilder, pathData);
                return getContextProperties();
        }
 
@@ -1725,10 +1726,9 @@ public class RestClientBuilder extends 
BeanContextBuilder {
        public RestClientBuilder headerPairs(String...pairs) {
                if (pairs.length % 2 != 0)
                        throw new RuntimeException("Odd number of parameters 
passed into headerPairs(String...)");
-               ArrayList<Header> l = new ArrayList<>();
+               HeaderListBuilder b  = getHeaderData();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(createHeader(stringify(pairs[i]), pairs[i+1]));
-               getHeaderData().append(l);
+                       b.append(pairs[i], pairs[i+1]);
                return this;
        }
 
@@ -1750,10 +1750,9 @@ public class RestClientBuilder extends 
BeanContextBuilder {
        public RestClientBuilder queryDataPairs(String...pairs) {
                if (pairs.length % 2 != 0)
                        throw new RuntimeException("Odd number of parameters 
passed into queryDataPairs(String...)");
-               ArrayList<NameValuePair> l = new ArrayList<>();
+               PartListBuilder b  = getQueryData();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(getQueryData().createPart(stringify(pairs[i]), 
pairs[i+1]));
-               getQueryData().append(l);
+                       b.append(pairs[i], pairs[i+1]);
                return this;
        }
 
@@ -1775,10 +1774,9 @@ public class RestClientBuilder extends 
BeanContextBuilder {
        public RestClientBuilder formDataPairs(String...pairs) {
                if (pairs.length % 2 != 0)
                        throw new RuntimeException("Odd number of parameters 
passed into formDataPairs(String...)");
-               ArrayList<NameValuePair> l = new ArrayList<>();
+               PartListBuilder b  = getFormData();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(getFormData().createPart(stringify(pairs[i]), 
pairs[i+1]));
-               getFormData().append(l);
+                       b.append(pairs[i], pairs[i+1]);
                return this;
        }
 
@@ -1800,10 +1798,9 @@ public class RestClientBuilder extends 
BeanContextBuilder {
        public RestClientBuilder pathDataPairs(String...pairs) {
                if (pairs.length % 2 != 0)
                        throw new RuntimeException("Odd number of parameters 
passed into pathDataPairs(String...)");
-               ArrayList<NameValuePair> l = new ArrayList<>();
+               PartListBuilder b  = getPathData();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(getFormData().createPart(stringify(pairs[i]), 
pairs[i+1]));
-               getPathData().set(l);
+                       b.append(pairs[i], pairs[i+1]);
                return this;
        }
 
@@ -3402,6 +3399,117 @@ public class RestClientBuilder extends 
BeanContextBuilder {
                return prependTo(RESTCLIENT_serializers, value);
        }
 
+       /**
+        * <i><l>RestClient</l> configuration property:&emsp;</i>  Skip empty 
form data.
+        *
+        * <p>
+        * When enabled, form data consisting of empty strings will be skipped 
on requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link FormData#skipIfEmpty()} annotation overrides this setting.
+        *
+        * @param value
+        *      The new value for this setting.
+        *      <br>The default is <jk>false</jk>.
+        * @return This object (for method chaining).
+        */
+       @FluentSetter
+       public RestClientBuilder skipEmptyFormData(boolean value) {
+               return set(RESTCLIENT_skipEmptyFormData, value);
+       }
+
+       /**
+        * <i><l>RestClient</l> configuration property:&emsp;</i>  Skip empty 
form data.
+        *
+        * <p>
+        * When enabled, form data consisting of empty strings will be skipped 
on requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link FormData#skipIfEmpty()} annotation overrides this setting.
+        *
+        * @return This object (for method chaining).
+        */
+       @FluentSetter
+       public RestClientBuilder skipEmptyFormData() {
+               return skipEmptyFormData(true);
+       }
+
+       /**
+        * <i><l>RestClient</l> configuration property:&emsp;</i>  Skip empty 
headers.
+        *
+        * <p>
+        * When enabled, headers consisting of empty strings will be skipped on 
requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link org.apache.juneau.http.annotation.Header#skipIfEmpty()} 
annotation overrides this setting.
+        *
+        * @param value
+        *      The new value for this setting.
+        *      <br>The default is <jk>false</jk>.
+        * @return This object (for method chaining).
+        */
+       @FluentSetter
+       public RestClientBuilder skipEmptyHeaders(boolean value) {
+               return set(RESTCLIENT_skipEmptyHeaders, value);
+       }
+
+       /**
+        * <i><l>RestClient</l> configuration property:&emsp;</i>  Skip empty 
headers.
+        *
+        * <p>
+        * When enabled, headers consisting of empty strings will be skipped on 
requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link org.apache.juneau.http.annotation.Header#skipIfEmpty()} 
annotation overrides this setting.
+        *
+        * @return This object (for method chaining).
+        */
+       @FluentSetter
+       public RestClientBuilder skipEmptyHeaders() {
+               return skipEmptyHeaders(true);
+       }
+
+       /**
+        * <i><l>RestClient</l> configuration property:&emsp;</i>  Skip empty 
query data.
+        *
+        * <p>
+        * When enabled, query parameters consisting of empty strings will be 
skipped on requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link Query#skipIfEmpty()} annotation overrides this setting.
+        *
+        * @param value
+        *      The new value for this setting.
+        *      <br>The default is <jk>false</jk>.
+        * @return This object (for method chaining).
+        */
+       @FluentSetter
+       public RestClientBuilder skipEmptyQueryData(boolean value) {
+               return set(RESTCLIENT_skipEmptyQueryData, value);
+       }
+
+       /**
+        * <i><l>RestClient</l> configuration property:&emsp;</i>  Skip empty 
query data.
+        *
+        * <p>
+        * When enabled, query parameters consisting of empty strings will be 
skipped on requests.
+        * Note that <jk>null</jk> values are already skipped.
+        *
+        * <p>
+        * The {@link Query#skipIfEmpty()} annotation overrides this setting.
+        *
+        * @return This object (for method chaining).
+        */
+       @FluentSetter
+       public RestClientBuilder skipEmptyQueryData() {
+               return skipEmptyQueryData(true);
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // BeanTraverse Properties
        
//-----------------------------------------------------------------------------------------------------------------
@@ -5725,50 +5833,4 @@ public class RestClientBuilder extends 
BeanContextBuilder {
                httpClientBuilder.evictIdleConnections(maxIdleTime, 
maxIdleTimeUnit);
                return this;
        }
-
-       
//------------------------------------------------------------------------------------------------------------------
-       // Utility methods.
-       
//------------------------------------------------------------------------------------------------------------------
-
-       /**
-        * Creates a header from the specified name/value pair.
-        *
-        * @param name The header name.
-        * @param value The header value.
-        * @return A new header, or <jk>null</jk> if the name was <jk>null</jk> 
or empty.
-        */
-       protected Header createHeader(String name, Object value) {
-               String n = stringify(name);
-               if (isEmpty(n))
-                       return null;
-               return getHeaderData().createHeader(n, value);
-       }
-
-       /**
-        * Creates a query parameter from the specified name/value pair.
-        *
-        * @param name The parameter name.
-        * @param value The parameter value.
-        * @return A new parameter, or <jk>null</jk> if the name was 
<jk>null</jk> or empty.
-        */
-       protected NameValuePair createQueryPart(String name, Object value) {
-               String n = stringify(name);
-               if (isEmpty(n))
-                       return null;
-               return getQueryData().createPart(n, value);
-       }
-
-       /**
-        * Creates a form-data parameter from the specified name/value pair.
-        *
-        * @param name The parameter name.
-        * @param value The parameter value.
-        * @return A new parameter, or <jk>null</jk> if the name was 
<jk>null</jk> or empty.
-        */
-       protected NameValuePair createFormDataPart(String name, Object value) {
-               String n = stringify(name);
-               if (isEmpty(n))
-                       return null;
-               return getFormData().createPart(n, value);
-       }
 }
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
index 45d3586..1e1833e 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
@@ -15,7 +15,6 @@ 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.internal.ExceptionUtils.*;
-import static org.apache.juneau.ListOperation.*;
 import static org.apache.juneau.httppart.HttpPartType.*;
 import static org.apache.juneau.http.HttpEntities.*;
 import static org.apache.juneau.http.HttpHeaders.*;
@@ -1226,10 +1225,9 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
        public RestRequest headerPairs(String...pairs) {
                if (pairs.length % 2 != 0)
                        throw runtimeException("Odd number of parameters passed 
into headerPairs(String...)");
-               List<Header> l = new ArrayList<>();
+               HeaderListBuilder b = getHeaderDataBuilder();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(createHeader(stringify(pairs[i]), pairs[i+1]));
-               getHeaderDataBuilder().add(APPEND, l);
+                       b.append(pairs[i], pairs[i+1]);
                return this;
        }
 
@@ -1256,10 +1254,9 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
        public RestRequest queryDataPairs(String...pairs) throws 
RestCallException {
                if (pairs.length % 2 != 0)
                        throw runtimeException("Odd number of parameters passed 
into queryDataPairs(String...)");
-               List<NameValuePair> l = new ArrayList<>();
+               PartListBuilder b = getQueryDataBuilder();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(createPart(QUERY, stringify(pairs[i]), 
pairs[i+1]));
-               getQueryDataBuilder().add(APPEND, l);
+                       b.append(pairs[i], pairs[i+1]);
                return this;
        }
 
@@ -1286,10 +1283,9 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
        public RestRequest formDataPairs(String...pairs) throws 
RestCallException {
                if (pairs.length % 2 != 0)
                        throw runtimeException("Odd number of parameters passed 
into formDataPairs(String...)");
-               List<NameValuePair> l = new ArrayList<>();
+               PartListBuilder b = getFormDataBuilder();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(createPart(FORMDATA, stringify(pairs[i]), 
pairs[i+1]));
-               getFormDataBuilder().add(APPEND, l);
+                       b.append(pairs[i], pairs[i+1]);
                return this;
        }
 
@@ -1318,19 +1314,21 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
        public RestRequest pathDataPairs(String...pairs) {
                if (pairs.length % 2 != 0)
                        throw runtimeException("Odd number of parameters passed 
into pathDataPairs(String...)");
-               List<NameValuePair> l = new ArrayList<>();
+               PartListBuilder b = getPathDataBuilder();
                for (int i = 0; i < pairs.length; i+=2)
-                       l.add(createPart(PATH, stringify(pairs[i]), 
pairs[i+1]));
-               getPathDataBuilder().add(SET, l);
+                       b.set(pairs[i], pairs[i+1]);
                return this;
        }
 
        /**
         * Appends multiple headers to the request from properties defined on a 
Java bean.
         *
+        * <p>
+        * Uses {@link PropertyNamerDUCS} for resolving the header names from 
property names.
+        *
         * <h5 class='section'>Example:</h5>
         * <p class='bcode w800'>
-        *      <ja>@Bean</ja>(propertyNamer=PropertyNamerDUCS.<jk>class</jk>) 
<jc>// Use Dash-UpperCase-Start property names.</jc>
+        *      <ja>@Bean</ja>
         *      <jk>public class<jk> MyHeaders {
         *              <jk>public</jk> String getFooBar() { <jk>return</jk> 
<js>"baz"</js>; }
         *              <jk>public</jk> Integer getQux() { <jk>return</jk> 123; 
}
@@ -1350,8 +1348,8 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
                if (! isBean(value))
                        throw runtimeException("Object passed into 
headersBean(Object) is not a bean.");
                HeaderListBuilder b = getHeaderDataBuilder();
-               for (Map.Entry<String,Object> e : toBeanMap(value).entrySet())
-                       b.append(createHeader(e.getKey(), 
stringify(e.getValue())));
+               for (Map.Entry<String,Object> e : toBeanMap(value, 
PropertyNamerDUCS.INSTANCE).entrySet())
+                       b.append(createHeader(e.getKey(), e.getValue()));
                return this;
        }
 
@@ -1380,7 +1378,7 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
                        throw runtimeException("Object passed into 
queryDataBean(Object) is not a bean.");
                PartListBuilder b = getQueryDataBuilder();
                for (Map.Entry<String,Object> e : toBeanMap(value).entrySet())
-                       b.append(createPart(QUERY, e.getKey(), 
stringify(e.getValue())));
+                       b.append(createPart(QUERY, e.getKey(), e.getValue()));
                return this;
        }
 
@@ -1409,7 +1407,7 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
                        throw runtimeException("Object passed into 
formDataBean(Object) is not a bean.");
                PartListBuilder b = getFormDataBuilder();
                for (Map.Entry<String,Object> e : toBeanMap(value).entrySet())
-                       b.append(createPart(FORMDATA, e.getKey(), 
stringify(e.getValue())));
+                       b.append(createPart(FORMDATA, e.getKey(), 
e.getValue()));
                return this;
        }
 
@@ -1438,7 +1436,7 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
                        throw runtimeException("Object passed into 
pathDataBean(Object) is not a bean.");
                PartListBuilder b = getPathDataBuilder();
                for (Map.Entry<String,Object> e : toBeanMap(value).entrySet())
-                       b.set(createPart(PATH, e.getKey(), 
stringify(e.getValue())));
+                       b.set(createPart(PATH, e.getKey(), e.getValue()));
                return this;
        }
 
@@ -3098,8 +3096,14 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
        protected NameValuePair createPart(String name, Object value, 
HttpPartType type, HttpPartSerializer serializer, HttpPartSchema schema, 
Boolean skipIfEmpty) {
                if (isEmpty(name))
                        return null;
-               if (skipIfEmpty == null)
-                       skipIfEmpty = client.isSkipEmptyHeaders();
+               if (skipIfEmpty == null) {
+                       if (type == QUERY)
+                               skipIfEmpty = client.isSkipEmptyQueryData();
+                       else if (type == FORMDATA)
+                               skipIfEmpty = client.isSkipEmptyFormData();
+                       else
+                               skipIfEmpty = false;
+               }
                if (serializer == null)
                        serializer = client.getPartSerializer();
                return new SerializedPart(name, value, type, 
getPartSerializerSession(serializer), schema, skipIfEmpty);
@@ -3149,16 +3153,17 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
        private class SimplePart implements NameValuePair {
                final String name;
                final String value;
-               final boolean skipIfEmpty;
 
                SimplePart(NameValuePair x, boolean skipIfEmpty) {
-                       if (x instanceof SerializedHeader)
-                               x = 
((SerializedHeader)x).copyWith(getPartSerializerSession(), null);
-                       if (x instanceof SerializedPart)
-                               x = 
((SerializedPart)x).copyWith(getPartSerializerSession(), null);
                        name = x.getName();
-                       value = x.getValue();
-                       this.skipIfEmpty = skipIfEmpty;
+                       if (x instanceof SerializedHeader) {
+                               value = 
((SerializedHeader)x).copyWith(getPartSerializerSession(), null).getValue();
+                       } else if (x instanceof SerializedPart) {
+                               value = 
((SerializedPart)x).copyWith(getPartSerializerSession(), null).getValue();
+                       } else {
+                               String v = x.getValue();
+                               value = (isEmpty(v) && skipIfEmpty) ? null : v;
+                       }
                }
 
                boolean isValid() {
@@ -3166,8 +3171,6 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
                                return false;
                        if (value == null)
                                return false;
-                       if (isEmpty(value) && skipIfEmpty)
-                               return false;
                        return true;
                }
 
@@ -3184,13 +3187,13 @@ public class RestRequest extends BeanSession implements 
HttpUriRequest, Configur
 
        private class SimpleQuery extends SimplePart {
                SimpleQuery(NameValuePair x) {
-                       super(x, client.isSkipEmptyQueryParameters());
+                       super(x, client.isSkipEmptyQueryData());
                }
        }
 
        private class SimpleFormData extends SimplePart {
                SimpleFormData(NameValuePair x) {
-                       super(x, client.isSkipEmptyFormDataParameters());
+                       super(x, client.isSkipEmptyFormData());
                }
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Test.java
index b183e9c..8b28992 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Test.java
@@ -94,7 +94,7 @@ public class RestClient_Test {
                RestClient.create().build().closeQuietly();
                RestClient.create().keepHttpClientOpen().build().close();
                RestClient.create().keepHttpClientOpen().build().closeQuietly();
-               
RestClient.create().set(RESTCLIENT_httpClient,null).keepHttpClientOpen().build().close();
+               
RestClient.create().set(RESTCLIENT_INTERNAL_httpClient,null).keepHttpClientOpen().build().close();
 
                ExecutorService es = new 
ThreadPoolExecutor(1,1,30,TimeUnit.SECONDS,new 
ArrayBlockingQueue<Runnable>(10));
                RestClient.create().executorService(es,true).build().close();

Reply via email to