You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2019/08/24 16:30:59 UTC

[cxf] branch 3.2.x-fixes updated (29faff7 -> 3187c8d)

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

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


    from 29faff7  Recording .gitmergeinfo Changes
     new 6176c7e  CXF-8089: Build Comma Separated Values in url from Array/List Query Param (#572)
     new 3187c8d  Recording .gitmergeinfo Changes

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |   3 +-
 .gitmergeinfo                                      |   2 +
 .../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  |  30 ++-
 .../org/apache/cxf/jaxrs/client/spring/clients.xml |   7 +-
 .../client/proxy/MicroProfileClientProxyImpl.java  |   2 +-
 .../org/apache/cxf/systest/jaxrs/BookStore.java    |  29 +++
 .../jaxrs/JAXRSClientServerQueryParamBookTest.java | 234 ++++++++++++++++++++
 ...RSClientServerQueryParamCollectionBookTest.java | 244 +++++++++++++++++++++
 18 files changed, 770 insertions(+), 49 deletions(-)
 create mode 100644 systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamBookTest.java
 create mode 100644 systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerQueryParamCollectionBookTest.java


[cxf] 01/02: CXF-8089: Build Comma Separated Values in url from Array/List Query Param (#572)

Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6176c7e869a23a158e7812ccc2cfa09366d01c24
Author: Andriy Redko <dr...@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)
    (cherry picked from commit c3adee999d4464d740f0740e2d5bd497b2b24af2)
    
    # Conflicts:
    #	rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/JAXRSClientFactoryBeanTest.java
    #	rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/spring/clients.xml
    #	rt/rs/microprofile-client/src/main/java/org/apache/cxf/microprofile/client/proxy/MicroProfileClientProxyImpl.java
---
 .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  |  30 ++-
 .../org/apache/cxf/jaxrs/client/spring/clients.xml |   7 +-
 .../client/proxy/MicroProfileClientProxyImpl.java  |   2 +-
 .../org/apache/cxf/systest/jaxrs/BookStore.java    |  29 +++
 .../jaxrs/JAXRSClientServerQueryParamBookTest.java | 234 ++++++++++++++++++++
 ...RSClientServerQueryParamCollectionBookTest.java | 244 +++++++++++++++++++++
 17 files changed, 768 insertions(+), 49 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 c72d263a..e23398f 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 c6c94f1..38b2814 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
@@ -341,7 +341,7 @@ public class UriBuilderImplTest extends Assert {
 
     @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 3b7a345..17f1d9c 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
@@ -117,7 +117,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 65ee66e..bdab2bc 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
@@ -220,7 +220,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);
 
@@ -243,11 +243,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;
     }
@@ -339,10 +339,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 f2b93ca..a7d802b 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 51866ed..af49c28 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 ca0af79..e333cdb 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 85cb49f..1d38f44 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
@@ -351,6 +351,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 c32f135..3b92061 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,6 +21,7 @@ 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;
 
@@ -28,6 +29,10 @@ import org.junit.After;
 import org.junit.Assert;
 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 extends Assert {
 
@@ -64,8 +69,29 @@ public class JAXRSClientFactoryBeanTest extends Assert {
         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"));
+        }
     }
-
 }
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 74ae059..6815916 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
@@ -45,4 +45,9 @@
             <entry key="Accept" value="text/xml"/>
         </jaxrs:headers>
     </jaxrs:client>
-</beans>
+    <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 1296cdc..d08ae13 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
@@ -61,7 +61,7 @@ public class MicroProfileClientProxyImpl extends ClientProxyImpl {
     public MicroProfileClientProxyImpl(URI baseURI, ClassLoader loader, ClassResourceInfo cri,
                                        boolean isRoot, boolean inheritHeaders, ExecutorService executorService,
                                        Configuration configuration, Object... varValues) {
-        super(new LocalClientState(baseURI), loader, cri, isRoot, inheritHeaders, varValues);
+        super(new LocalClientState(baseURI, configuration.getProperties()), loader, cri, isRoot, inheritHeaders, varValues);
         cfg.getRequestContext().put(EXECUTOR_SERVICE_PROPERTY, executorService);
         cfg.getRequestContext().putAll(configuration.getProperties());
     }
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 bfa5851..a536a80 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


[cxf] 02/02: Recording .gitmergeinfo Changes

Posted by re...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 3187c8d3a2bad5f05b6802718c2198b9647e1a07
Author: reta <dr...@gmail.com>
AuthorDate: Sat Aug 24 11:56:13 2019 -0400

    Recording .gitmergeinfo Changes
---
 .gitmergeinfo | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitmergeinfo b/.gitmergeinfo
index b341dcf..27c317f 100644
--- a/.gitmergeinfo
+++ b/.gitmergeinfo
@@ -511,6 +511,7 @@ B ca3f8a54ea7718dc8f830fcafb9216a75cb93379
 B ca6b25e140c08e9d05b51bd7aefbf3b87bc6a6af
 B ca8ce28170e659a55aaa0cbb3facc835375d334f
 B cb0585be6862a919249639f7c15c81273e5ef6d7
+B cb084ca830b19c1ddb0df459bc0c06cbc7ebb197
 B cb0d65d853797d7302c9e600f8a12f54a36df1a8
 B cb332a23ad51e738ae9a3d5fe9f39518e04194e9
 B cb6b19c7ce86ee4e7e7559502f6fe4b940968c0b
@@ -704,6 +705,7 @@ M ad5c6ad246f9155fec7096de7701de02d757c0f2
 M af6a95f5b7efc141cc55792e10d05d471905cf32
 M b9300944381ebe10f7c5fe86d55b25b8292372fd
 M c13381d891b56fe5a2b56f6f9f6f6b2cc8a10d5d
+M c3adee999d4464d740f0740e2d5bd497b2b24af2
 M c453a20f958b49119029b229a908f94848fb7f2d
 M c79d8ac83379b9f80e124823fbb569141cc5e608
 M cb2a360153c6670925f91fdfa9980594715d62e2