CXF-8089: Build Comma Separated Values in url from Array/List Query Param (#572)
(cherry picked from commit 768c0959b6e1882badeb55387f1d2fc34b57237e)
(cherry picked from commit c3adee999d4464d740f0740e2d5bd497b2b24af2)
diff --git a/.gitignore b/.gitignore
index fdc3d89..4f59107 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,5 @@
*~
node_modules/
derby.log
+.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 fbda422..21c63b0 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.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;
@@ -62,6 +64,8 @@
private Map<String, Object> resolvedTemplatesPathEnc;
private Map<String, Object> resolvedEncodedTemplates;
+ private boolean queryValueIsCollection;
+
/**
* Creates builder with empty URI.
*/
@@ -69,6 +73,13 @@
}
/**
+ * 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,7 +436,8 @@
builder.schemeSpecificPart = schemeSpecificPart;
builder.leadingSlash = leadingSlash;
builder.originalPathEmpty = originalPathEmpty;
- builder.resolvedEncodedTemplates =
+ builder.queryValueIsCollection = queryValueIsCollection;
+ builder.resolvedEncodedTemplates =
resolvedEncodedTemplates == null ? null : new HashMap<String, Object>(resolvedEncodedTemplates);
builder.resolvedTemplates =
resolvedTemplates == null ? null : new HashMap<String, Object>(resolvedTemplates);
@@ -838,27 +850,62 @@
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);
}
- } else {
- val = URITemplate.createExactTemplate(val).encodeLiteralCharacters(isQuery);
+
+ if (!val.isEmpty()) {
+ b.append(val);
+ }
}
- 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 f89533a..43d03a5 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 @@
@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 c472f32..5d61c29 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
@@ -111,7 +111,17 @@
boolean isRoot,
boolean inheritHeaders,
Object... varValues) {
- this(new LocalClientState(baseURI), loader, cri, isRoot, inheritHeaders, varValues);
+ this(baseURI, loader, cri, isRoot, inheritHeaders, Collections.<String, Object>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 de7a36a..4b64b7e 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,20 @@
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
+ */
+ ClientState newState(URI baseURI,
+ MultivaluedMap<String, String> headers,
+ MultivaluedMap<String, String> templates,
+ Map<String, Object> properties);
}
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 167ae73..302d9e3 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.net.URI;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import javax.ws.rs.core.MultivaluedMap;
@@ -88,6 +89,19 @@
/**
* 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 @@
* @return typed proxy
*/
public static <T> T create(String baseAddress, Class<T> cls, List<?> providers, boolean threadSafe) {
+ return create(baseAddress, cls, providers, Collections.<String, Object>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 @@
}
} 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 7936733..1c75173 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
@@ -219,7 +219,7 @@
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);
@@ -242,11 +242,11 @@
private 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;
+ return headers != null
+ ? initialState.newState(URI.create(getAddress()), headers, null, getProperties()) : initialState;
} else {
return null;
}
@@ -339,10 +339,10 @@
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 217244f..5aca35b 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 @@
private Response response;
private URI baseURI;
private UriBuilder currentBuilder;
-
+ private Map<String, Object> properties;
+
public LocalClientState() {
}
public LocalClientState(URI baseURI) {
+ this(baseURI, Collections.<String, Object>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.<String, Object>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 @@
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 void setBaseURI(URI baseURI) {
this.baseURI = baseURI;
- resetCurrentUri();
+ resetCurrentUri(Collections.<String, Object>emptyMap());
}
public URI getBaseURI() {
@@ -123,18 +146,17 @@
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);
@@ -149,7 +171,13 @@
state.setTemplates(newTemplateParams);
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 d6b4313..be7d72b 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 @@
private long timeToKeepState;
public ThreadLocalClientState(String baseURI) {
- this.initialState = new LocalClientState(URI.create(baseURI));
+ this(baseURI, Collections.<String, Object>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.<String, Object>emptyMap());
+ }
+
+ public ThreadLocalClientState(String baseURI, long timeToKeepState, Map<String, Object> properties) {
+ this.initialState = new LocalClientState(URI.create(baseURI), properties);
this.timeToKeepState = timeToKeepState;
}
@@ -107,6 +115,16 @@
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);
if (checkpointMap != null) {
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 4f8d1aa..312e589 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
@@ -86,11 +86,19 @@
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.<String, Object>emptyMap());
+ }
+
+ protected WebClient(String baseAddress, Map<String, Object> properties) {
+ this(convertStringToURI(baseAddress), properties);
}
protected WebClient(URI baseURI) {
- this(new LocalClientState(baseURI));
+ this(baseURI, Collections.<String, Object>emptyMap());
+ }
+
+ protected WebClient(URI baseURI, Map<String, Object> properties) {
+ this(new LocalClientState(baseURI, properties));
}
protected WebClient(ClientState state) {
@@ -106,8 +114,17 @@
* @param baseAddress baseAddress
*/
public static WebClient create(String baseAddress) {
+ return create(baseAddress, Collections.<String, Object>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();
}
@@ -143,10 +160,23 @@
* @param threadSafe if true ThreadLocalClientState is used
*/
public static WebClient create(String baseAddress, List<?> providers, boolean threadSafe) {
+ return create(baseAddress, providers, Collections.<String, Object>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 85d5e62..5efb0b1 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 @@
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 2bdb107..2d9a28d 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 @@
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.junit.After;
@@ -29,6 +30,11 @@
import org.springframework.context.support.ClassPathXmlApplicationContext;
+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 {
@After
@@ -64,8 +70,29 @@
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");
+
+ 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/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 5362094..89fea53 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
@@ -184,9 +184,13 @@
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() {
@@ -395,6 +399,11 @@
return new BookStoreSub(this);
}
+ @Path("/querysub")
+ public BookStoreQuerySub getQuerySub() {
+ return new BookStoreQuerySub();
+ }
+
@GET
@Path("/twoBeanParams/{id}")
@Produces("application/xml")
@@ -2188,6 +2197,26 @@
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..42faaff
--- /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.<Long>asList(1L, 2L, 3L), Arrays.<Double>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.<Long>asList(), Arrays.<Double>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..0a8e6f4
--- /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.<Long>asList(1L, 2L, 3L), Arrays.<Double>asList());
+ assertEquals(123L, book.getId());
+ }
+
+ @Test
+ public void testListOfLongAndDoubleQueryEmpty() throws Exception {
+ BookStore client = createClient();
+ Book book = client.getBookFromListOfLongAndDouble(Arrays.<Long>asList(), Arrays.<Double>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.<String, Object>singletonMap("expand.query.value.as.collection", "true"));
+ } else {
+ return WebClient.create("http://localhost:" + PORT, Collections.emptyList(),
+ Collections.<String, Object>singletonMap("expand.query.value.as.collection", "true"), true);
+ }
+ }
+
+ private BookStore createClient() {
+ if (threadSafe == null) {
+ return JAXRSClientFactory.create("http://localhost:" + PORT, BookStore.class,
+ Collections.<String, Object>singletonMap("expand.query.value.as.collection", "true"));
+ } else {
+ return JAXRSClientFactory.create("http://localhost:" + PORT, BookStore.class, Collections.emptyList(),
+ Collections.<String, Object>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