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

reta pushed a commit to branch 3.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit c3adee999d4464d740f0740e2d5bd497b2b24af2
Author: Andriy Redko <drr...@gmail.com>
AuthorDate: Sat Aug 24 08:14:28 2019 -0400

    CXF-8089: Build Comma Separated Values in url from Array/List Query Param 
(#572)
    
    (cherry picked from commit 768c0959b6e1882badeb55387f1d2fc34b57237e)
---
 .gitignore                                         |   3 +-
 .../org/apache/cxf/jaxrs/impl/UriBuilderImpl.java  |  81 +++++--
 .../apache/cxf/jaxrs/impl/UriBuilderImplTest.java  |   2 +-
 .../apache/cxf/jaxrs/client/ClientProxyImpl.java   |  12 +-
 .../org/apache/cxf/jaxrs/client/ClientState.java   |  19 ++
 .../cxf/jaxrs/client/JAXRSClientFactory.java       |  32 ++-
 .../cxf/jaxrs/client/JAXRSClientFactoryBean.java   |  10 +-
 .../apache/cxf/jaxrs/client/LocalClientState.java  |  54 +++--
 .../cxf/jaxrs/client/ThreadLocalClientState.java   |  21 +-
 .../org/apache/cxf/jaxrs/client/WebClient.java     |  36 ++-
 .../apache/cxf/jaxrs/client/spec/ClientImpl.java   |   1 +
 .../client/spring/JAXRSClientFactoryBeanTest.java  |  28 ++-
 .../org/apache/cxf/jaxrs/client/spring/clients.xml |   5 +
 .../client/proxy/MicroProfileClientProxyImpl.java  |   2 +-
 .../org/apache/cxf/systest/jaxrs/BookStore.java    |  29 +++
 .../jaxrs/JAXRSClientServerQueryParamBookTest.java | 234 ++++++++++++++++++++
 ...RSClientServerQueryParamCollectionBookTest.java | 244 +++++++++++++++++++++
 17 files changed, 765 insertions(+), 48 deletions(-)

diff --git a/.gitignore b/.gitignore
index b625761..6beab61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,5 @@ velocity.log
 bin/
 node_modules/
 derby.log
-.pmdruleset.xml
\ No newline at end of file
+.pmdruleset.xml
+.sts4-cache/
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java
index 561536b..d95bdbf 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java
@@ -39,13 +39,15 @@ import javax.ws.rs.core.PathSegment;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriBuilderException;
 
+import org.apache.cxf.common.util.PropertyUtils;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.cxf.jaxrs.model.URITemplate;
 import org.apache.cxf.jaxrs.utils.HttpUtils;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
 
 public class UriBuilderImpl extends UriBuilder implements Cloneable {
-
+    private static final String EXPAND_QUERY_VALUE_AS_COLLECTION = 
"expand.query.value.as.collection";
+    
     private String scheme;
     private String userInfo;
     private int port = -1;
@@ -61,7 +63,9 @@ public class UriBuilderImpl extends UriBuilder implements 
Cloneable {
     private Map<String, Object> resolvedTemplates;
     private Map<String, Object> resolvedTemplatesPathEnc;
     private Map<String, Object> resolvedEncodedTemplates;
-
+    
+    private boolean queryValueIsCollection;
+    
     /**
      * Creates builder with empty URI.
      */
@@ -69,6 +73,13 @@ public class UriBuilderImpl extends UriBuilder implements 
Cloneable {
     }
 
     /**
+     * Creates builder with empty URI and properties
+     */
+    public UriBuilderImpl(Map<String, Object> properties) {
+        queryValueIsCollection = PropertyUtils.isTrue(properties, 
EXPAND_QUERY_VALUE_AS_COLLECTION);
+    }
+
+    /**
      * Creates builder initialized with given URI.
      *
      * @param uri initial value for builder
@@ -425,6 +436,7 @@ public class UriBuilderImpl extends UriBuilder implements 
Cloneable {
         builder.schemeSpecificPart = schemeSpecificPart;
         builder.leadingSlash = leadingSlash;
         builder.originalPathEmpty = originalPathEmpty;
+        builder.queryValueIsCollection = queryValueIsCollection;
         builder.resolvedEncodedTemplates =
             resolvedEncodedTemplates == null ? null : new HashMap<String, 
Object>(resolvedEncodedTemplates);
         builder.resolvedTemplates =
@@ -842,27 +854,62 @@ public class UriBuilderImpl extends UriBuilder implements 
Cloneable {
         StringBuilder b = new StringBuilder();
         for (Iterator<Map.Entry<String, List<String>>> it = 
map.entrySet().iterator(); it.hasNext();) {
             Map.Entry<String, List<String>> entry = it.next();
-            for (Iterator<String> sit = entry.getValue().iterator(); 
sit.hasNext();) {
-                String val = sit.next();
-                b.append(entry.getKey());
-                if (val != null) {
-                    boolean templateValue = val.startsWith("{") && 
val.endsWith("}");
-                    if (!templateValue) {
-                        val = HttpUtils.encodePartiallyEncoded(val, isQuery);
-                        if (!isQuery) {
-                            val = val.replaceAll("/", "%2F");
+            
+            // Expand query parameter as "name=v1,v2,v3" 
+            if (isQuery && queryValueIsCollection) {
+                b.append(entry.getKey()).append('=');
+                
+                for (Iterator<String> sit = entry.getValue().iterator(); 
sit.hasNext();) {
+                    String val = sit.next();
+                    
+                    if (val != null) {
+                        boolean templateValue = val.startsWith("{") && 
val.endsWith("}");
+                        if (!templateValue) {
+                            val = HttpUtils.encodePartiallyEncoded(val, 
isQuery);
+                            if (!isQuery) {
+                                val = val.replaceAll("/", "%2F");
+                            }
+                        } else {
+                            val = 
URITemplate.createExactTemplate(val).encodeLiteralCharacters(isQuery);
+                        }
+                        
+                        if (!val.isEmpty()) {
+                            b.append(val);
                         }
-                    } else {
-                        val = 
URITemplate.createExactTemplate(val).encodeLiteralCharacters(isQuery);
                     }
-                    b.append('=');
-                    if (!val.isEmpty()) {
-                        b.append(val);
+                    if (sit.hasNext()) {
+                        b.append(',');
                     }
                 }
-                if (sit.hasNext() || it.hasNext()) {
+                
+                if (it.hasNext()) {
                     b.append(separator);
                 }
+            } else {
+                // Expand query parameter as "name=v1&name=v2&name=v3", or use 
dedicated 
+                // separator for matrix parameters
+                for (Iterator<String> sit = entry.getValue().iterator(); 
sit.hasNext();) {
+                    String val = sit.next();
+                    b.append(entry.getKey());
+                    if (val != null) {
+                        boolean templateValue = val.startsWith("{") && 
val.endsWith("}");
+                        if (!templateValue) {
+                            val = HttpUtils.encodePartiallyEncoded(val, 
isQuery);
+                            if (!isQuery) {
+                                val = val.replaceAll("/", "%2F");
+                            }
+                        } else {
+                            val = 
URITemplate.createExactTemplate(val).encodeLiteralCharacters(isQuery);
+                        }
+                        b.append('=');
+                        if (!val.isEmpty()) {
+                            b.append(val);
+                        }
+                    }
+                    if (sit.hasNext() || it.hasNext()) {
+                        b.append(separator);
+                    }
+                }
             }
         }
         return b.length() > 0 ? b.toString() : null;
diff --git 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java
 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java
index afcc78d..261bf5f 100644
--- 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java
+++ 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java
@@ -343,7 +343,7 @@ public class UriBuilderImplTest {
 
     @Test(expected = IllegalArgumentException.class)
     public void testCtorNull() throws Exception {
-        new UriBuilderImpl(null);
+        new UriBuilderImpl((URI)null);
     }
 
     @Test(expected = IllegalArgumentException.class)
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
index f857593..a4f8cc4 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java
@@ -119,7 +119,17 @@ public class ClientProxyImpl extends AbstractClient 
implements
                            boolean isRoot,
                            boolean inheritHeaders,
                            Object... varValues) {
-        this(new LocalClientState(baseURI), loader, cri, isRoot, 
inheritHeaders, varValues);
+        this(baseURI, loader, cri, isRoot, inheritHeaders, 
Collections.emptyMap(), varValues);
+    }
+
+    public ClientProxyImpl(URI baseURI,
+            ClassLoader loader,
+            ClassResourceInfo cri,
+            boolean isRoot,
+            boolean inheritHeaders,
+            Map<String, Object> properties,
+            Object... varValues) {
+        this(new LocalClientState(baseURI, properties), loader, cri, isRoot, 
inheritHeaders, varValues);
     }
 
     public ClientProxyImpl(ClientState initialState,
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java
index 83ea6e2..8fb6c0b 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientState.java
@@ -19,6 +19,7 @@
 package org.apache.cxf.jaxrs.client;
 
 import java.net.URI;
+import java.util.Map;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -115,4 +116,22 @@ public interface ClientState {
     ClientState newState(URI baseURI,
                          MultivaluedMap<String, String> headers,
                          MultivaluedMap<String, String> templates);
+    
+    /**
+     * The factory method for creating a new state.
+     * Example, proxy and WebClient.fromClient will use this method when 
creating
+     * subresource proxies and new web clients respectively to ensure thet stay
+     * thread-local if needed
+     * @param baseURI baseURI
+     * @param headers request headers, can be null
+     * @param templates initial templates map, can be null
+     * @param additional properties, could be null
+     * @return client state
+     */
+    default ClientState newState(URI baseURI,
+                         MultivaluedMap<String, String> headers,
+                         MultivaluedMap<String, String> templates,
+                         Map<String, Object> properties) {
+        return newState(baseURI, headers, templates);
+    }
 }
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
index f90e44a..4ba3a59 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactory.java
@@ -22,6 +22,7 @@ import java.lang.reflect.InvocationHandler;
 import java.net.URI;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import javax.ws.rs.core.MultivaluedMap;
 
@@ -88,6 +89,19 @@ public final class JAXRSClientFactory {
 
     /**
      * Creates a proxy
+     * @param baseAddress baseAddres
+     * @param cls resource class, if not interface then a CGLIB proxy will be 
created
+     * @param properties additional properties
+     * @return typed proxy
+     */
+    public static <T> T create(String baseAddress, Class<T> cls, Map<String, 
Object> properties) {
+        JAXRSClientFactoryBean bean = getBean(baseAddress, cls, null);
+        bean.setProperties(properties);
+        return bean.create(cls);
+    }
+
+    /**
+     * Creates a proxy
      * @param baseAddress baseAddress
      * @param cls resource class, if not interface then a CGLIB proxy will be 
created
      * @param configLocation classpath location of the configuration resource
@@ -135,10 +149,24 @@ public final class JAXRSClientFactory {
      * @return typed proxy
      */
     public static <T> T create(String baseAddress, Class<T> cls, List<?> 
providers, boolean threadSafe) {
+        return create(baseAddress, cls, providers, Collections.emptyMap(), 
threadSafe);
+    }
+    /**
+     * Creates a thread safe proxy
+     * @param baseAddress baseAddress
+     * @param cls proxy class, if not interface then a CGLIB proxy will be 
created
+     * @param providers list of providers
+     * @param threadSafe if true then a thread-safe proxy will be created
+     * @param properties additional properties
+     * @return typed proxy
+     */
+    public static <T> T create(String baseAddress, Class<T> cls, List<?> 
providers, 
+            Map<String, Object> properties, boolean threadSafe) {
         JAXRSClientFactoryBean bean = getBean(baseAddress, cls, null);
         bean.setProviders(providers);
+        bean.setProperties(properties);
         if (threadSafe) {
-            bean.setInitialState(new ThreadLocalClientState(baseAddress));
+            bean.setInitialState(new ThreadLocalClientState(baseAddress, 
properties));
         }
         return bean.create(cls);
     }
@@ -362,7 +390,7 @@ public final class JAXRSClientFactory {
             }
         } else {
             MultivaluedMap<String, String> headers = inheritHeaders ? 
client.getHeaders() : null;
-            bean.setInitialState(clientState.newState(client.getCurrentURI(), 
headers, null));
+            bean.setInitialState(clientState.newState(client.getCurrentURI(), 
headers, null, bean.getProperties()));
             proxy = bean.create(cls);
         }
         WebClient.copyProperties(WebClient.client(proxy), client);
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
index e66ce82..5783e2b 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/JAXRSClientFactoryBean.java
@@ -221,7 +221,7 @@ public class JAXRSClientFactoryBean extends 
AbstractJAXRSFactoryBean {
             Endpoint ep = createEndpoint();
             
this.getServiceFactory().sendEvent(FactoryBeanListener.Event.PRE_CLIENT_CREATE, 
ep);
             ClientState actualState = getActualState();
-            WebClient client = actualState == null ? new 
WebClient(getAddress())
+            WebClient client = actualState == null ? new 
WebClient(getAddress(), getProperties())
                 : new WebClient(actualState);
             initClient(client, ep, actualState == null);
 
@@ -244,11 +244,11 @@ public class JAXRSClientFactoryBean extends 
AbstractJAXRSFactoryBean {
 
     protected ClientState getActualState() {
         if (threadSafe) {
-            initialState = new ThreadLocalClientState(getAddress(), 
timeToKeepState);
+            initialState = new ThreadLocalClientState(getAddress(), 
timeToKeepState, getProperties());
         }
         if (initialState != null) {
             return headers != null
-                ? initialState.newState(URI.create(getAddress()), headers, 
null) : initialState;
+                ? initialState.newState(URI.create(getAddress()), headers, 
null, getProperties()) : initialState;
         }
         return null;
     }
@@ -341,10 +341,10 @@ public class JAXRSClientFactoryBean extends 
AbstractJAXRSFactoryBean {
                                                 ClientState actualState, 
Object[] varValues) {
         if (actualState == null) {
             return new ClientProxyImpl(URI.create(getAddress()), proxyLoader, 
cri, isRoot,
-                                    inheritHeaders, varValues);
+                                    inheritHeaders, getProperties(), 
varValues);
         } else {
             return new ClientProxyImpl(actualState, proxyLoader, cri, isRoot,
-                                    inheritHeaders, varValues);
+                                    inheritHeaders, getProperties(), 
varValues);
         }
     }
 
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java
index ecbe2fc..53c13fb 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/LocalClientState.java
@@ -19,6 +19,9 @@
 package org.apache.cxf.jaxrs.client;
 
 import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
@@ -41,19 +44,38 @@ public class LocalClientState implements ClientState {
     private Response response;
     private URI baseURI;
     private UriBuilder currentBuilder;
+    private Map<String, Object> properties;
 
     public LocalClientState() {
 
     }
 
     public LocalClientState(URI baseURI) {
+        this(baseURI, Collections.emptyMap());
+    }
+    
+    public LocalClientState(URI baseURI, Map<String, Object> properties) {
         this.baseURI = baseURI;
-        resetCurrentUri();
+        
+        if (properties != null) {
+            this.properties = new HashMap<>(properties);
+        }
+        
+        resetCurrentUri(properties);
     }
 
     public LocalClientState(URI baseURI, URI currentURI) {
+        this(baseURI, currentURI, Collections.emptyMap()); 
+    }
+
+    public LocalClientState(URI baseURI, URI currentURI, Map<String, Object> 
properties) {
         this.baseURI = baseURI;
-        this.currentBuilder = new UriBuilderImpl().uri(currentURI);
+        
+        if (properties != null) {
+            this.properties = new HashMap<>(properties);
+        }
+        
+        this.currentBuilder = new UriBuilderImpl(properties).uri(currentURI);
     }
 
     public LocalClientState(LocalClientState cs) {
@@ -63,13 +85,14 @@ public class LocalClientState implements ClientState {
 
         this.baseURI = cs.baseURI;
         this.currentBuilder = cs.currentBuilder != null ? 
cs.currentBuilder.clone() : null;
+        this.properties = cs.properties;
     }
 
-    private void resetCurrentUri() {
+    private void resetCurrentUri(Map<String, Object> props) {
         if (isSupportedScheme(baseURI)) {
-            this.currentBuilder = new UriBuilderImpl().uri(baseURI);
+            this.currentBuilder = new UriBuilderImpl(props).uri(baseURI);
         } else {
-            this.currentBuilder = new UriBuilderImpl().uri("/");
+            this.currentBuilder = new UriBuilderImpl(props).uri("/");
         }
     }
 
@@ -83,7 +106,7 @@ public class LocalClientState implements ClientState {
 
     public void setBaseURI(URI baseURI) {
         this.baseURI = baseURI;
-        resetCurrentUri();
+        resetCurrentUri(Collections.emptyMap());
     }
 
     public URI getBaseURI() {
@@ -123,18 +146,17 @@ public class LocalClientState implements ClientState {
     public void reset() {
         requestHeaders.clear();
         response = null;
-        currentBuilder = new UriBuilderImpl().uri(baseURI);
+        currentBuilder = new UriBuilderImpl(properties).uri(baseURI);
         templates = null;
     }
-
-    public ClientState newState(URI currentURI,
-                                MultivaluedMap<String, String> headers,
-                                MultivaluedMap<String, String> templatesMap) {
+    
+    public ClientState newState(URI currentURI, MultivaluedMap<String, String> 
headers,
+            MultivaluedMap<String, String> templatesMap, Map<String, Object> 
props) {
         ClientState state = null;
         if (isSupportedScheme(currentURI)) {
-            state = new LocalClientState(currentURI);
+            state = new LocalClientState(currentURI, props);
         } else {
-            state = new LocalClientState(baseURI, currentURI);
+            state = new LocalClientState(baseURI, currentURI, props);
         }
         if (headers != null) {
             state.setRequestHeaders(headers);
@@ -150,6 +172,12 @@ public class LocalClientState implements ClientState {
         return state;
     }
 
+    public ClientState newState(URI currentURI,
+                                MultivaluedMap<String, String> headers,
+                                MultivaluedMap<String, String> templatesMap) {
+        return newState(currentURI, headers, templatesMap, properties);
+    }
+
     private static boolean isSupportedScheme(URI uri) {
         return !StringUtils.isEmpty(uri.getScheme())
             && (uri.getScheme().startsWith(HTTP_SCHEME) || 
uri.getScheme().startsWith(WS_SCHEME));
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java
 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java
index 6bc825a..77b1b7e 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ThreadLocalClientState.java
@@ -43,11 +43,19 @@ public class ThreadLocalClientState implements ClientState {
     private long timeToKeepState;
 
     public ThreadLocalClientState(String baseURI) {
-        this.initialState = new LocalClientState(URI.create(baseURI));
+        this(baseURI, Collections.emptyMap());
+    }
+    
+    public ThreadLocalClientState(String baseURI, Map<String, Object> 
properties) {
+        this.initialState = new LocalClientState(URI.create(baseURI), 
properties);
     }
 
     public ThreadLocalClientState(String baseURI, long timeToKeepState) {
-        this.initialState = new LocalClientState(URI.create(baseURI));
+        this(baseURI, timeToKeepState, Collections.emptyMap());
+    }
+
+    public ThreadLocalClientState(String baseURI, long timeToKeepState, 
Map<String, Object> properties) {
+        this.initialState = new LocalClientState(URI.create(baseURI), 
properties);
         this.timeToKeepState = timeToKeepState;
     }
 
@@ -106,6 +114,15 @@ public class ThreadLocalClientState implements ClientState 
{
         LocalClientState ls = 
(LocalClientState)getState().newState(currentURI, headers, templates);
         return new ThreadLocalClientState(ls, timeToKeepState);
     }
+    
+    @Override
+    public ClientState newState(URI currentURI,
+            MultivaluedMap<String, String> headers,
+            MultivaluedMap<String, String> templates,
+            Map<String, Object> properties) {
+        LocalClientState ls = 
(LocalClientState)getState().newState(currentURI, headers, templates, 
properties);
+        return new ThreadLocalClientState(ls, timeToKeepState);
+    }
 
     private void removeThreadLocalState(Thread t) {
         state.remove(t);
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
index fbbddf9..0f0c36c 100644
--- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
+++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/WebClient.java
@@ -90,11 +90,19 @@ public class WebClient extends AbstractClient {
     private static final String WEB_CLIENT_OPERATION_REPORTING = 
"enable.webclient.operation.reporting";
     private BodyWriter bodyWriter = new BodyWriter();
     protected WebClient(String baseAddress) {
-        this(convertStringToURI(baseAddress));
+        this(convertStringToURI(baseAddress), Collections.emptyMap());
+    }
+    
+    protected WebClient(String baseAddress, Map<String, Object> properties) {
+        this(convertStringToURI(baseAddress), properties);
     }
 
     protected WebClient(URI baseURI) {
-        this(new LocalClientState(baseURI));
+        this(baseURI, Collections.emptyMap());
+    }
+
+    protected WebClient(URI baseURI, Map<String, Object> properties) {
+        this(new LocalClientState(baseURI, properties));
     }
 
     protected WebClient(ClientState state) {
@@ -110,8 +118,17 @@ public class WebClient extends AbstractClient {
      * @param baseAddress baseAddress
      */
     public static WebClient create(String baseAddress) {
+        return create(baseAddress, Collections.emptyMap());
+    }
+
+    /**
+     * Creates WebClient
+     * @param baseAddress baseAddress
+     */
+    public static WebClient create(String baseAddress, Map<String, Object> 
properties) {
         JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
         bean.setAddress(baseAddress);
+        bean.setProperties(properties);
         return bean.createWebClient();
     }
 
@@ -147,10 +164,23 @@ public class WebClient extends AbstractClient {
      * @param threadSafe if true ThreadLocalClientState is used
      */
     public static WebClient create(String baseAddress, List<?> providers, 
boolean threadSafe) {
+        return create(baseAddress, providers, Collections.emptyMap(), 
threadSafe);
+    }
+    
+    /**
+     * Creates WebClient
+     * @param baseAddress baseURI
+     * @param providers list of providers
+     * @param threadSafe if true ThreadLocalClientState is used
+     * @param properties additional properties
+     */
+    public static WebClient create(String baseAddress, List<?> providers, 
+            Map<String, Object> properties, boolean threadSafe) {
         JAXRSClientFactoryBean bean = getBean(baseAddress, null);
         bean.setProviders(providers);
+        bean.setProperties(properties);
         if (threadSafe) {
-            bean.setInitialState(new ThreadLocalClientState(baseAddress));
+            bean.setInitialState(new ThreadLocalClientState(baseAddress, 
properties));
         }
         return bean.createWebClient();
     }
diff --git 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientImpl.java 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientImpl.java
index de4d2ca..7b830a4 100644
--- 
a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientImpl.java
+++ 
b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/spec/ClientImpl.java
@@ -345,6 +345,7 @@ public class ClientImpl implements Client {
             if (targetClient == null) {
                 JAXRSClientFactoryBean bean = new JAXRSClientFactoryBean();
                 bean.setAddress(uri.toString());
+                bean.setProperties(configProps);
                 Boolean threadSafe = 
getBooleanValue(configProps.get(THREAD_SAFE_CLIENT_PROP));
                 if (threadSafe == null) {
                     threadSafe = DEFAULT_THREAD_SAFETY_CLIENT_STATUS;
diff --git 
a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
 
b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
index e514ce1..fee3c2b 100644
--- 
a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
+++ 
b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
@@ -21,14 +21,17 @@ package org.apache.cxf.jaxrs.client.spring;
 import javax.xml.namespace.QName;
 
 import org.apache.cxf.BusFactory;
+import org.apache.cxf.jaxrs.client.Client;
 import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 
 import org.junit.After;
 import org.junit.Test;
 
+import static org.hamcrest.CoreMatchers.endsWith;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
 
 
 public class JAXRSClientFactoryBeanTest {
@@ -66,8 +69,29 @@ public class JAXRSClientFactoryBeanTest {
         assertEquals("Get a wrong map size", cfb.getHeaders().size(), 1);
         assertEquals("Get a wrong username", cfb.getUsername(), "username");
         assertEquals("Get a wrong password", cfb.getPassword(), "password");
-        ctx.close();
+        
+        bean = ctx.getBean("client2.proxyFactory");
+        assertNotNull(bean);
+        cfb = (JAXRSClientFactoryBean) bean;
+        assertNotNull(cfb.getProperties());
+        assertEquals("Get a wrong map size", cfb.getProperties().size(), 1);
 
+        ctx.close();
+    }
+    
+    @Test
+    public void testClientProperties() throws Exception {
+        try (ClassPathXmlApplicationContext ctx =
+                new ClassPathXmlApplicationContext(new String[] 
{"/org/apache/cxf/jaxrs/client/spring/clients.xml"})) {
+            Client bean = (Client) ctx.getBean("client2");
+            assertNotNull(bean);
+            assertThat(bean.query("list", "1").query("list", 
"2").getCurrentURI().toString(),
+                endsWith("?list=1,2"));
+            
+            bean = (Client) ctx.getBean("client1");
+            assertNotNull(bean);
+            assertThat(bean.query("list", "1").query("list", 
"2").getCurrentURI().toString(),
+                endsWith("?list=1&list=2"));
+        }
     }
-
 }
\ No newline at end of file
diff --git 
a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml 
b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml
index 5162562..6e5e564 100644
--- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml
+++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml
@@ -49,4 +49,9 @@
             <entry key="Accept" value="text/xml"/>
         </jaxrs:headers>
     </jaxrs:client>
+    <jaxrs:client id="client2" 
serviceClass="org.apache.cxf.jaxrs.resources.BookStore" 
address="http://localhost:9000/foo";>
+        <jaxrs:properties>
+            <entry key="expand.query.value.as.collection" value="true" />
+        </jaxrs:properties>
+    </jaxrs:client>
 </beans>
\ No newline at end of file
diff --git 
a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/proxy/MicroProfileClientProxyImpl.java
 
b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/proxy/MicroProfileClientProxyImpl.java
index aa5a771..50c7cf9 100644
--- 
a/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/proxy/MicroProfileClientProxyImpl.java
+++ 
b/rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/proxy/MicroProfileClientProxyImpl.java
@@ -102,7 +102,7 @@ public class MicroProfileClientProxyImpl extends 
ClientProxyImpl {
                                        boolean isRoot, boolean inheritHeaders, 
ExecutorService executorService,
                                        Configuration configuration, 
CDIInterceptorWrapper interceptorWrapper, 
                                        Object... varValues) {
-        super(new LocalClientState(baseURI), loader, cri, isRoot, 
inheritHeaders, varValues);
+        super(new LocalClientState(baseURI, configuration.getProperties()), 
loader, cri, isRoot, inheritHeaders, varValues);
         this.interceptorWrapper = interceptorWrapper;
         init(executorService, configuration);
     }
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
index d4d0cec..d2001ef 100644
--- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
+++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStore.java
@@ -185,9 +185,13 @@ public class BookStore {
         String dStr = dBuilder.toString();
         if (!lStr.equals(dStr)) {
             throw new InternalServerErrorException();
+        } else if ("".equalsIgnoreCase(lStr)) {
+            lStr = "0";
         }
+        
         return new Book("cxf", Long.parseLong(lStr));
     }
+    
     @GET
     @Path("/")
     public Book getBookRoot() {
@@ -391,6 +395,11 @@ public class BookStore {
     public BookStoreSub getBeanParamBookSub() {
         return new BookStoreSub(this);
     }
+    
+    @Path("/querysub")
+    public BookStoreQuerySub getQuerySub() {
+        return new BookStoreQuerySub();
+    }
 
     @GET
     @Path("/twoBeanParams/{id}")
@@ -2225,6 +2234,26 @@ public class BookStore {
             return bookStore.getBeanParamBook(bean);
         }
     }
+    
+    public static class BookStoreQuerySub {
+        @GET
+        @Path("/listofstrings")
+        @Produces("text/xml")
+        public Book getBookFromListStrings(@QueryParam("value") List<String> 
value) {
+            final StringBuilder builder = new StringBuilder();
+            
+            for (String v : value) {
+                if (builder.length() > 0) {
+                    builder.append(' ');
+                }
+                
+                builder.append(v);
+            }
+            
+            return new Book(builder.toString(), 0L);
+        }
+    }
+
 }
 
 
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamBookTest.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamBookTest.java
new file mode 100644
index 0000000..5bf08f8
--- /dev/null
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamBookTest.java
@@ -0,0 +1,234 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.ext.xml.XMLSource;
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class JAXRSClientServerQueryParamBookTest extends 
AbstractBusClientServerTestBase {
+    public static final String PORT = BookServer.PORT;
+    private final Boolean threadSafe;
+    
+    public JAXRSClientServerQueryParamBookTest(Boolean threadSafe) {
+        this.threadSafe = threadSafe;
+    }
+    
+    @Parameters(name = "Client is thread safe = {0}")
+    public static Collection<Boolean> data() {
+        return Arrays.asList(new Boolean[] {null, true, false});
+    }
+
+    @BeforeClass
+    public static void startServers() throws Exception {
+        AbstractResourceInfo.clearAllMaps();
+        assertTrue("server did not launch correctly",
+                launchServer(BookServer.class, true));
+        createStaticBus();
+    }
+
+    @Test
+    public void testListOfLongAndDoubleQuery() throws Exception {
+        BookStore client = createClient();
+        Book book = client.getBookFromListOfLongAndDouble(Arrays.asList(1L, 
2L, 3L), Arrays.asList());
+        assertEquals(123L, book.getId());
+    }
+    
+    @Test
+    public void testListOfLongAndDoubleQueryWebClient() throws Exception {
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+                .path("/bookstore/listoflonganddouble")
+                .query("value", Arrays.asList(1L, 2L, 3L))
+                .accept("text/xml")
+                .get();
+
+        assertThat(wc.getCurrentURI().toString(), 
endsWith("value=1&value=2&value=3"));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals(123L, Long.parseLong(source.getValue("Book/id")));
+        }
+    }
+
+    @Test
+    public void testListOfLongAndDoubleQueryAsManyWebClient() throws Exception 
{
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+                .path("/bookstore/listoflonganddouble")
+                .query("value", "1")
+                .query("value", "2")
+                .query("value", "3")
+                .accept("text/xml")
+                .get();
+
+        assertThat(wc.getCurrentURI().toString(), 
endsWith("value=1&value=2&value=3"));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals(123L, Long.parseLong(source.getValue("Book/id")));
+        }
+    }
+    
+    @Test
+    public void testListOfLongAndDoubleQueryAsString() throws Exception {
+        final URIBuilder builder = new URIBuilder("http://localhost:"; + PORT + 
"/bookstore/listoflonganddouble");
+        builder.setCustomQuery("value=1,2,3");
+
+        final CloseableHttpClient client = HttpClientBuilder.create().build();
+        HttpGet get = new HttpGet(builder.build());
+        get.addHeader("Accept", "text/xml");
+
+        try (CloseableHttpResponse response = client.execute(get)) {
+            // should not succeed since "parse.query.value.as.collection" 
contextual property is not set
+            assertEquals(404, response.getStatusLine().getStatusCode());
+        }
+    }
+    
+    @Test
+    public void testListOfLongAndDoubleQueryEmptyWebClient() throws Exception {
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+                .path("/bookstore/listoflonganddouble")
+                .query("value", "")
+                .accept("text/xml")
+                .get();
+
+        assertThat(wc.getCurrentURI().toString(), endsWith("value="));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals(0L, Long.parseLong(source.getValue("Book/id")));
+        }
+    }
+    
+    @Test
+    public void testListOfLongAndDoubleQueryEmpty() throws Exception {
+        BookStore client = createClient();
+        Book book = client.getBookFromListOfLongAndDouble(Arrays.asList(), 
Arrays.asList());
+        assertEquals(0L, book.getId());
+    }
+
+    @Test
+    public void testListOfStringsWebClient() throws Exception {
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+                .path("/bookstore/querysub/listofstrings")
+                .query("value", "this is")
+                .query("value", "the book")
+                .query("value", "title")
+                .accept("text/xml")
+                .get();
+
+        assertThat(wc.getCurrentURI().toString(), 
endsWith("value=this+is&value=the+book&value=title"));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals("this is the book title", 
source.getValue("Book/name"));
+        }
+    }
+    
+    @Test
+    public void testListOfStringsJaxrsClient() throws Exception {
+        WebTarget client = createJaxrsClient();
+        
+        Response r = client
+                .path("/bookstore/querysub/listofstrings")
+                .queryParam("value", "this is")
+                .queryParam("value", "the book")
+                .queryParam("value", "title")
+                .request()
+                .accept("text/xml")
+                .get();
+
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals("this is the book title", 
source.getValue("Book/name"));
+        }
+    }
+
+    @Test
+    public void testListOfStrings() throws Exception {
+        BookStore client = createClient();
+        
+        Book book = client.getQuerySub().getBookFromListStrings(
+            Arrays.asList("this is", "the book", "title"));
+
+        assertEquals("this is the book title", book.getName());
+    }
+    
+    private WebClient createWebClient() {
+        if (threadSafe == null) {
+            return WebClient.create("http://localhost:"; + PORT);
+        } else {
+            return WebClient.create("http://localhost:"; + PORT, 
Collections.emptyList(), threadSafe);
+        }
+    }
+    
+    private BookStore createClient() {
+        if (threadSafe == null) {
+            return JAXRSClientFactory.create("http://localhost:"; + PORT, 
BookStore.class);
+        } else {
+            return JAXRSClientFactory.create("http://localhost:"; + PORT, 
BookStore.class, 
+                Collections.emptyList(), threadSafe);
+        }
+    }
+    
+    private WebTarget createJaxrsClient() {
+        if (threadSafe == null) {
+            return ClientBuilder
+                .newClient()
+                .target("http://localhost:"; + PORT);
+        } else {
+            return ClientBuilder
+                .newClient()
+                .property("thread.safe.client", threadSafe)
+                .target("http://localhost:"; + PORT);
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamCollectionBookTest.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamCollectionBookTest.java
new file mode 100644
index 0000000..b5a7588
--- /dev/null
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamCollectionBookTest.java
@@ -0,0 +1,244 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.ext.xml.XMLSource;
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class JAXRSClientServerQueryParamCollectionBookTest extends 
AbstractBusClientServerTestBase {
+    public static final String PORT = BookServer.PORT;
+    private final Boolean threadSafe;
+    
+    public JAXRSClientServerQueryParamCollectionBookTest(Boolean threadSafe) {
+        this.threadSafe = threadSafe;
+    }
+
+    @Parameters(name = "Client is thread safe = {0}")
+    public static Collection<Boolean> data() {
+        return Arrays.asList(new Boolean[] {null, true, false});
+    }
+    
+    @BeforeClass
+    public static void startServers() throws Exception {
+        AbstractResourceInfo.clearAllMaps();
+        assertTrue("server did not launch correctly", 
+            launchServer(new 
BookServer(Collections.singletonMap("parse.query.value.as.collection", 
"true"))));
+        createStaticBus();
+    }
+
+    @Test
+    public void testListOfLongAndDoubleQuery() throws Exception {
+        BookStore client = createClient();
+        Book book = client.getBookFromListOfLongAndDouble(Arrays.asList(1L, 
2L, 3L), Arrays.asList());
+        assertEquals(123L, book.getId());
+    }
+
+    @Test
+    public void testListOfLongAndDoubleQueryEmpty() throws Exception {
+        BookStore client = createClient();
+        Book book = client.getBookFromListOfLongAndDouble(Arrays.asList(), 
Arrays.asList());
+        assertEquals(0L, book.getId());
+    }
+
+    @Test
+    public void testListOfLongAndDoubleQueryWebClient() throws Exception {
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+            .path("/bookstore/listoflonganddouble")
+            .query("value", Arrays.asList(1L, 2L, 3L))
+            .accept("text/xml")
+            .get();
+
+        assertThat(wc.getCurrentURI().toString(), endsWith("value=1,2,3"));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals(123L, Long.parseLong(source.getValue("Book/id")));
+        }
+    }
+
+    @Test
+    public void testListOfLongAndDoubleQueryAsManyWebClient() throws Exception 
{
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+                .path("/bookstore/listoflonganddouble")
+                .query("value", "1")
+                .query("value", "2")
+                .query("value", "3")
+                .accept("text/xml")
+                .get();
+
+        assertThat(wc.getCurrentURI().toString(), endsWith("value=1,2,3"));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals(123L, Long.parseLong(source.getValue("Book/id")));
+        }
+    }
+    
+    @Test
+    public void testListOfStringsWebClient() throws Exception {
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+                .path("/bookstore/querysub/listofstrings")
+                .query("value", "this is")
+                .query("value", "the book")
+                .query("value", "title")
+                .accept("text/xml")
+                .get();
+
+        assertThat(wc.getCurrentURI().toString(), 
endsWith("value=this+is,the+book,title"));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals("this is the book title", 
source.getValue("Book/name"));
+        }
+    }
+
+    @Test
+    public void testListOfStringsJaxrsClient() throws Exception {
+        WebTarget client = createJaxrsClient();
+        
+        Response r = client
+                .path("/bookstore/querysub/listofstrings")
+                .queryParam("value", "this is")
+                .queryParam("value", "the book")
+                .queryParam("value", "title")
+                .request()
+                .accept("text/xml")
+                .get();
+
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals("this is the book title", 
source.getValue("Book/name"));
+        }
+    }
+
+    @Test
+    public void testListOfStrings() throws Exception {
+        BookStore bookStore = createClient();
+        
+        Book book = bookStore.getQuerySub().getBookFromListStrings(
+            Arrays.asList("this is", "the book", "title"));
+
+        assertEquals("this is the book title", book.getName());
+    }
+
+    @Test
+    public void testListOfLongAndDoubleQueryEmptyWebClient() throws Exception {
+        WebClient wc = createWebClient();
+        
+        Response r = wc
+                .path("/bookstore/listoflonganddouble")
+                .query("value", "")
+                .accept("text/xml")
+                .get();
+
+        assertThat(wc.getCurrentURI().toString(), endsWith("value="));
+        try (InputStream is = (InputStream)r.getEntity()) {
+            XMLSource source = new XMLSource(is);
+            assertEquals(0L, Long.parseLong(source.getValue("Book/id")));
+        }
+    }
+    
+    @Test
+    public void testListOfLongAndDoubleQueryAsString() throws Exception {
+        final URIBuilder builder = new URIBuilder("http://localhost:"; + PORT + 
"/bookstore/listoflonganddouble");
+        builder.setCustomQuery("value=1,2,3");
+
+        final CloseableHttpClient client = HttpClientBuilder.create().build();
+        HttpGet get = new HttpGet(builder.build());
+        get.addHeader("Accept", "text/xml");
+
+        try (CloseableHttpResponse response = client.execute(get)) {
+            final byte[] content = 
EntityUtils.toByteArray(response.getEntity());
+            try (InputStream is = new ByteArrayInputStream(content)) {
+                XMLSource source = new XMLSource(is);
+                assertEquals(123L, Long.parseLong(source.getValue("Book/id")));
+            }
+        }
+    }
+    
+    private WebClient createWebClient() {
+        if (threadSafe == null) {
+            return WebClient.create("http://localhost:"; + PORT, 
+                Collections.singletonMap("expand.query.value.as.collection", 
"true"));
+        } else {
+            return WebClient.create("http://localhost:"; + PORT, 
Collections.emptyList(),
+                Collections.singletonMap("expand.query.value.as.collection", 
"true"), true);
+        }
+    }
+    
+    private BookStore createClient() {
+        if (threadSafe == null) {
+            return JAXRSClientFactory.create("http://localhost:"; + PORT, 
BookStore.class,
+                Collections.singletonMap("expand.query.value.as.collection", 
"true"));
+        } else {
+            return JAXRSClientFactory.create("http://localhost:"; + PORT, 
BookStore.class, Collections.emptyList(),
+                Collections.singletonMap("expand.query.value.as.collection", 
"true"), threadSafe);
+        }
+    }
+    
+    private WebTarget createJaxrsClient() {
+        if (threadSafe == null) {
+            return ClientBuilder
+                .newClient()
+                .property("expand.query.value.as.collection", "true")
+                .target("http://localhost:"; + PORT);
+        } else {
+            return ClientBuilder
+                .newClient()
+                .property("expand.query.value.as.collection", "true")
+                .property("thread.safe.client", threadSafe)
+                .target("http://localhost:"; + PORT);
+        }
+    }
+}
\ No newline at end of file

Reply via email to