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