From ee264a8d0991ad918b00761e7674847e20bd4ba8 Mon Sep 17 00:00:00 2001
From: Scott Leberknight <174812+sleberknight@users.noreply.github.com>
Date: Mon, 30 Jan 2023 09:44:01 -0500
Subject: [PATCH] Remove Lombok Delegate from WebTargetHelper (#860)
* Replace Lombok Delegate on the wrapped WebTarget with "real" code
* Make this class implement WebTarget
* Update class-level Javadocs to remove the "Limitations" section
* Fix method javadocs that incorrectly stated "this" is returned
* Fix a few minor grammatical errors in comments
Closes #859
---
.../jaxrs/client/WebTargetHelper.java | 314 ++++++++++++---
.../jaxrs/client/WebTargetHelperTest.java | 367 +++++++++++++++++-
2 files changed, 619 insertions(+), 62 deletions(-)
diff --git a/src/main/java/org/kiwiproject/jaxrs/client/WebTargetHelper.java b/src/main/java/org/kiwiproject/jaxrs/client/WebTargetHelper.java
index 265db8e0..9b504867 100644
--- a/src/main/java/org/kiwiproject/jaxrs/client/WebTargetHelper.java
+++ b/src/main/java/org/kiwiproject/jaxrs/client/WebTargetHelper.java
@@ -11,12 +11,16 @@
import com.google.common.annotations.Beta;
import com.google.common.annotations.VisibleForTesting;
-import lombok.experimental.Delegate;
import org.apache.commons.lang3.StringUtils;
import javax.ws.rs.client.Client;
+import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Configuration;
+import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -37,66 +41,30 @@
*
* Usage example (assuming {@link WebTargetClientHelper#withClient(Client) withClient} is statically imported):
*
- * withClient(client).target("/search")
+ * var response = withClient(client).target("/search")
* .queryParamRequireNotBlank("q", query)
* .queryParamIfNotBlank("sort", sort)
* .queryParamIfNotBlank("page", page)
* .queryParamIfNotBlank("limit", limit)
- * .queryParamFilterNotBlank("langs", langs);
- *
- * Limitations
- * This is a limited wrapper around {@link WebTarget} that provides enhanced functionality only for
- * adding query parameters. Only the methods defined in this class are chainable, i.e. once you call a method defined
- * in the regular {@link Client} interface, you leave the {@link WebTargetHelper} context.
- *
- * For example you can NOT do this:
- *
- * withClient(client).target("/search")
- * .queryParamRequireNotBlank("q", query)
- * .queryParam("sort", sort) // after this, only Client methods are accessible!!! WON'T COMPILE
- * .queryParamIfNotBlank("page", page)
- * .queryParamIfNotBlank("limit", limit)
- * .queryParamFilterNotBlank("langs", langs);
- *
- * With the current basic implementation, this means certain usages will be awkward. For example, when using
- * both parameter templates and query parameters, the query parameters need to be added first, for the reason
- * given above about leaving the {@link WebTargetHelper} context. For example:
- *
- * var response = withClient(client).target("/users/{userId}/trades/{tradeId}")
- * .queryParamIfNotBlank("displayCurrency", currency)
- * .queryParamIfNotNull("showLimitPrice", showLimitPrice)
- * .resolveTemplate("userId", userId) // after this, only Client methods are accessible!!!
- * .resolveTemplate("tradeId", tradeId)
+ * .queryParamFilterNotBlank("langs", languages)
* .request()
* .get();
*
- * One way to get around this restriction is to use methods from {@link WebTarget} as normal, and then wrap it
- * with a {@link WebTargetHelper} to add query parameters. The above example would then look like:
+ * This class implements {@link WebTarget}, and overridden methods return {@link WebTargetHelper}, so you can chain
+ * methods as you normally would. For example, using {@link #withWebTarget(WebTarget) withWebTarget}:
*
- * var pathResolvedTarget = client.target("/users/{userId}/trades/{tradeId}")
- * .resolveTemplate("userId", userId)
- * .resolveTemplate("tradeId", tradeId);
- *
- * var response = withWebTarget(pathResolvedTarget)
- * .queryParamIfNotBlank("displayCurrency", currency)
- * .queryParamIfNotNull("showLimitPrice", showLimitPrice)
+ * var response = withWebTarget(originalTarget)
+ * .path("/resolve/{id}")
+ * .resolveTemplate("id", 42)
+ * .queryParamIfNotBlank("format", format)
+ * .queryParamIfNotNull("force", force)
* .request()
* .get();
*
- * This usage allows for full functionality of {@link WebTarget} while still getting the enhanced query parameter
- * features of this class. It isn't perfect, but it works and, in our opinion anyway, doesn't intrude too much on
- * building JAX-RS requests. In other words, we think it is a decent trade off.
- *
- * @implNote Internally this uses Lombok's {@link lombok.Delegate}, which is why this class doesn't implement {@link WebTarget}
- * directly. While this lets us easily delegate method calls to a {@link WebTarget}, it also restricts what we can do
- * here, and is the primary reason why there are usage restrictions. However, in our general usage this implementation
- * has been enough for our needs. Nevertheless, this is currently marked with the Guava {@link Beta} annotation in case
- * we change our minds on the implementation.
*/
@Beta
-public class WebTargetHelper {
+public class WebTargetHelper implements WebTarget {
- @Delegate
private final WebTarget webTarget;
/**
@@ -140,7 +108,7 @@ public static WebTargetHelper withWebTarget(WebTarget webTarget) {
*
* @param name the parameter name
* @param value the parameter value
- * @return this instance
+ * @return a new instance
* @throws IllegalArgumentException if name is blank or value is null
*/
public WebTargetHelper queryParamRequireNotNull(String name, Object value) {
@@ -156,7 +124,7 @@ public WebTargetHelper queryParamRequireNotNull(String name, Object value) {
*
* @param name the parameter name
* @param value the parameter value
- * @return this instance
+ * @return a new instance if name and value are not blank, otherwise this instance
*/
public WebTargetHelper queryParamIfNotNull(String name, Object value) {
if (isBlank(name) || isNull(value)) {
@@ -172,7 +140,7 @@ public WebTargetHelper queryParamIfNotNull(String name, Object value) {
*
* @param name the parameter name
* @param values one or more parameter values
- * @return this instance
+ * @return a new instance if name is not blank and values is not null or empty, otherwise this instance
*/
public WebTargetHelper queryParamFilterNotNull(String name, Object... values) {
if (isBlank(name) || isNullOrEmpty(values)) {
@@ -187,7 +155,7 @@ public WebTargetHelper queryParamFilterNotNull(String name, Object... values) {
*
* @param name the parameter name
* @param values one or more parameter values
- * @return this instance
+ * @return a new instance if name is not blank and values is not null or empty, otherwise this instance
*/
public WebTargetHelper queryParamFilterNotNull(String name, List values) {
if (isBlank(name) || isNullOrEmpty(values)) {
@@ -202,7 +170,7 @@ public WebTargetHelper queryParamFilterNotNull(String name, List values)
*
* @param name the parameter name
* @param stream containing one or more parameter values
- * @return this instance
+ * @return a new instance if name is not blank and stream is not null, otherwise this instance
*/
public WebTargetHelper queryParamFilterNotNull(String name, Stream stream) {
if (isBlank(name) || isNull(stream)) {
@@ -238,7 +206,7 @@ public WebTargetHelper queryParamRequireNotBlank(String name, String value) {
*
* @param name the parameter name
* @param value the parameter value
- * @return this instance
+ * @return a new instance if name is and value are not blank, otherwise this instance
*/
public WebTargetHelper queryParamIfNotBlank(String name, String value) {
if (isBlank(name) || isBlank(value)) {
@@ -254,7 +222,7 @@ public WebTargetHelper queryParamIfNotBlank(String name, String value) {
*
* @param name the parameter name
* @param values one or more parameter values
- * @return this instance
+ * @return a new instance if name is not blank and values is not null or empty, otherwise this instance
*/
public WebTargetHelper queryParamFilterNotBlank(String name, String... values) {
if (isBlank(name) || isNullOrEmpty(values)) {
@@ -269,7 +237,7 @@ public WebTargetHelper queryParamFilterNotBlank(String name, String... values) {
*
* @param name the parameter name
* @param values one or more parameter values
- * @return this instance
+ * @return a new instance if name is not blank and values is not null or empty, otherwise this instance
*/
public WebTargetHelper queryParamFilterNotBlank(String name, List values) {
if (isBlank(name) || isNullOrEmpty(values)) {
@@ -284,7 +252,7 @@ public WebTargetHelper queryParamFilterNotBlank(String name, List values
*
* @param name the parameter name
* @param stream containing one or more parameter values
- * @return this instance
+ * @return a new instance if name is not blank and stream is not null, otherwise this instance
*/
public WebTargetHelper queryParamFilterNotBlank(String name, Stream stream) {
if (isBlank(name) || isNull(stream)) {
@@ -304,7 +272,7 @@ public WebTargetHelper queryParamFilterNotBlank(String name, Stream stre
*
* @param parameters a map representing the query parameters
* @param the type of keys in the map
- * @return this instance
+ * @return a new instance if parameters is not null or empty, otherwise this instance
* @implNote This method is distinct from {@link #queryParamsFromMultivaluedMap(MultivaluedMap)} because the
* {@link MultivaluedMap} interface extends the regular Java {@link Map} and under certain circumstances this
* method will be called even when the argument is actually a {@link MultivaluedMap}. By having separate and
@@ -325,7 +293,7 @@ public WebTargetHelper queryParamsFromMap(Map parameters) {
// NOTE: The above is effectively a foldLeft, which Java Streams does not have. The 3-arg reduce version is a lot
// more difficult to understand than a simple loop with a mutable variable that we keep replacing. In addition,
- // the reduce version cannot be strictly correct, since we cannot define a combiner function which is "an
+ // the "reduce" version cannot be strictly correct, since we cannot define a combiner function which is "an
// associative, non-interfering, stateless function for combining" two WebTargetHelper instances. Instead, we
// would require it is only used on a sequential (non-parallel) stream. Regardless, the implementation is less
// clear than just a loop with a mutable variable, which is why this is not using the streams API. While the
@@ -341,7 +309,7 @@ public WebTargetHelper queryParamsFromMap(Map parameters) {
*
* @param parameters a multivalued representing the query parameters
* @param the type of keys in the map
- * @return this instance
+ * @return a new instance if parameters is not null or empty, otherwise this instance
* @implNote See implementation note on {@link #queryParamsFromMap(Map)} for an explanation why this method is
* named separately and distinctly.
*/
@@ -357,7 +325,7 @@ public WebTargetHelper queryParamsFromMultivaluedMap(MultivaluedMap in order to get the compiler to call queryParamFilterNotNull(String, List). If
// the cast is not done, the compiler instead "thinks" the value is an Object and selects the
// queryParamFilterNotNull(String, Object...) method, which does not work as expected because the value
- // of the MultivaluedMap is supposed to be a List. The real reason this happens is because the type
+ // of the MultivaluedMap is supposed to be a List. The real reason this happens is that the type
// erasure of List is simply List. The compiler then (incorrectly from what we'd like to happen) selects
// the vararg method as the one to call, since the raw List type is an Object, not a List.
var targetHelper = this;
@@ -368,4 +336,230 @@ public WebTargetHelper queryParamsFromMultivaluedMap(MultivaluedMap templateValues) {
+ var newWebTarget = this.webTarget.resolveTemplates(templateValues);
+ return new WebTargetHelper(newWebTarget);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper resolveTemplates(Map templateValues, boolean encodeSlashInPath) {
+ var newWebTarget = this.webTarget.resolveTemplates(templateValues, encodeSlashInPath);
+ return new WebTargetHelper(newWebTarget);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper resolveTemplatesFromEncoded(Map templateValues) {
+ var newWebTarget = this.webTarget.resolveTemplatesFromEncoded(templateValues);
+ return new WebTargetHelper(newWebTarget);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper matrixParam(String name, Object... values) {
+ var newWebTarget = this.webTarget.matrixParam(name, values);
+ return new WebTargetHelper(newWebTarget);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper queryParam(String name, Object... values) {
+ var newWebTarget = this.webTarget.queryParam(name, values);
+ return new WebTargetHelper(newWebTarget);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Invocation.Builder request() {
+ return this.webTarget.request();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Invocation.Builder request(String... acceptedResponseTypes) {
+ return this.webTarget.request(acceptedResponseTypes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Invocation.Builder request(MediaType... acceptedResponseTypes) {
+ return this.webTarget.request(acceptedResponseTypes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Configuration getConfiguration() {
+ return this.webTarget.getConfiguration();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper property(String name, Object value) {
+ this.webTarget.property(name, value);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Class> componentClass) {
+ this.webTarget.register(componentClass);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Class> componentClass, int priority) {
+ this.webTarget.register(componentClass, priority);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Class> componentClass, Class>... contracts) {
+ this.webTarget.register(componentClass, contracts);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Class> componentClass, Map, Integer> contracts) {
+ this.webTarget.register(componentClass, contracts);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Object component) {
+ this.webTarget.register(component);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Object component, int priority) {
+ this.webTarget.register(component, priority);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Object component, Class>... contracts) {
+ this.webTarget.register(component, contracts);
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public WebTargetHelper register(Object component, Map, Integer> contracts) {
+ this.webTarget.register(component, contracts);
+ return this;
+ }
}
diff --git a/src/test/java/org/kiwiproject/jaxrs/client/WebTargetHelperTest.java b/src/test/java/org/kiwiproject/jaxrs/client/WebTargetHelperTest.java
index 360373b3..d2024509 100644
--- a/src/test/java/org/kiwiproject/jaxrs/client/WebTargetHelperTest.java
+++ b/src/test/java/org/kiwiproject/jaxrs/client/WebTargetHelperTest.java
@@ -1,10 +1,15 @@
package org.kiwiproject.jaxrs.client;
import static com.google.common.collect.Lists.newArrayList;
+import static javax.ws.rs.Priorities.AUTHORIZATION;
+import static javax.ws.rs.Priorities.ENTITY_CODER;
+import static javax.ws.rs.Priorities.USER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.kiwiproject.jaxrs.client.WebTargetHelper.withWebTarget;
+import org.glassfish.jersey.client.filter.CsrfProtectionFilter;
+import org.glassfish.jersey.client.filter.EncodingFeature;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@@ -14,11 +19,18 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.kiwiproject.collect.KiwiMaps;
+import javax.annotation.Priority;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.ClientRequestContext;
+import javax.ws.rs.client.ClientRequestFilter;
+import javax.ws.rs.client.ClientResponseContext;
+import javax.ws.rs.client.ClientResponseFilter;
import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import java.util.List;
@@ -120,7 +132,7 @@ void shouldThrow_WhenGivenNullValue() {
.withMessage("value cannot be null for parameter bar");
// NOTE: Only the first null value encountered will be reported, since there is
- // no easy and clean way to accumulate errors that I can see. Thus only the 'bar'
+ // no easy and clean way to accumulate errors that I can see. Thus, only the 'bar'
// parameter is reported in the exception.
}
@@ -296,7 +308,7 @@ void shouldThrow_WhenGivenNullValue() {
.withMessage("value cannot be blank for parameter bar");
// NOTE: Only the first null value encountered will be reported, since there is
- // no easy and clean way to accumulate errors that I can see. Thus only the 'bar'
+ // no easy and clean way to accumulate errors that I can see. Thus, only the 'bar'
// parameter is reported in the exception.
}
@@ -692,6 +704,357 @@ void whenLongValues() {
}
}
+ @Nested
+ class ShouldDelegateTo {
+
+ @Test
+ void getUri() {
+ var uri = withWebTarget(originalWebTarget).getUri();
+
+ assertThat(uri).isEqualTo(originalWebTarget.getUri());
+ }
+
+ @Test
+ void getUriBuilder() {
+ var uriBuilder = withWebTarget(originalWebTarget).getUriBuilder();
+
+ assertThat(uriBuilder.build()).isEqualTo(originalWebTarget.getUriBuilder().build());
+ }
+
+ @Test
+ void path() {
+ var newWebTarget = withWebTarget(originalWebTarget).path("/more");
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+ assertThat(newWebTarget.getUri()).isEqualTo(originalWebTarget.path("/more").getUri());
+ }
+
+ @Test
+ void resolveTemplate_Name_Value() {
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .path("more/{someParam}")
+ .resolveTemplate("someParam", "theValue");
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .path("more/{someParam}")
+ .resolveTemplate("someParam", "theValue")
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void resolveTemplate_Name_Value_EncodeSlashInPath(boolean encodeSlashInPath) {
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .path("more/{someParam}")
+ .resolveTemplate("someParam", "theValue/theSubValue", encodeSlashInPath);
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .path("more/{someParam}")
+ .resolveTemplate("someParam", "theValue/theSubValue", encodeSlashInPath)
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+ @Test
+ void resolveTemplateFromEncoded_Name_Value() {
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .path("more/{moreValue}")
+ .resolveTemplateFromEncoded("moreValue", "the%Value%foo");
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .path("more/{moreValue}")
+ .resolveTemplateFromEncoded("moreValue", "the%Value%foo")
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+ @Test
+ void resolveTemplates_TemplateValues() {
+ Map templateValues = Map.of(
+ "moreValue", "1",
+ "evenMoreValue", "2",
+ "andSomeMoreValue", "3"
+ );
+
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .path("more/{moreValue}")
+ .path("evenMore/{evenMoreValue}")
+ .path("andSomeMore/{andSomeMoreValue}")
+ .resolveTemplates(templateValues);
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .path("more/{moreValue}")
+ .path("evenMore/{evenMoreValue}")
+ .path("andSomeMore/{andSomeMoreValue}")
+ .resolveTemplates(templateValues)
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void resolveTemplates_TemplateValues_EncodeSlashInPath(boolean encodeSlashInPath) {
+ Map templateValues = Map.of(
+ "moreValue", "val/1",
+ "evenMoreValue", "val/2",
+ "andSomeMoreValue", "val/3"
+ );
+
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .path("more/{moreValue}")
+ .path("evenMore/{evenMoreValue}")
+ .path("andSomeMore/{andSomeMoreValue}")
+ .resolveTemplates(templateValues, encodeSlashInPath);
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .path("more/{moreValue}")
+ .path("evenMore/{evenMoreValue}")
+ .path("andSomeMore/{andSomeMoreValue}")
+ .resolveTemplates(templateValues, encodeSlashInPath)
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+ @Test
+ void resolveTemplatesFromEncoded_TemplateValues() {
+ Map templateValues = Map.of(
+ "moreValue", "val%1",
+ "evenMoreValue", "val%2",
+ "andSomeMoreValue", "val%3"
+ );
+
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .path("more/{moreValue}")
+ .path("evenMore/{evenMoreValue}")
+ .path("andSomeMore/{andSomeMoreValue}")
+ .resolveTemplatesFromEncoded(templateValues);
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .path("more/{moreValue}")
+ .path("evenMore/{evenMoreValue}")
+ .path("andSomeMore/{andSomeMoreValue}")
+ .resolveTemplatesFromEncoded(templateValues)
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+ @Test
+ void matrixParam() {
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .matrixParam("p1", "a", "b", "c")
+ .matrixParam("p2", 1, 3, 5);
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .matrixParam("p1", "a", "b", "c")
+ .matrixParam("p2", 1, 3, 5)
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+ @Test
+ void queryParam() {
+ var newWebTarget = withWebTarget(originalWebTarget)
+ .queryParam("p1", "a", "b", "c")
+ .queryParam("p2", 1, 3, 5)
+ .queryParam("p3", 42);
+
+ assertThat(newWebTarget).isNotSameAs(originalWebTarget);
+
+ var expectedUri = originalWebTarget
+ .queryParam("p1", "a", "b", "c")
+ .queryParam("p2", 1, 3, 5)
+ .queryParam("p3", 42)
+ .getUri();
+ assertThat(newWebTarget.getUri()).isEqualTo(expectedUri);
+ }
+
+ @Test
+ void request() {
+ var invocationBuilder = withWebTarget(originalWebTarget).request();
+ assertThat(invocationBuilder).isNotNull();
+ }
+
+ @Test
+ void request_StringAcceptedResponseTypes() {
+ var invocationBuilder = withWebTarget(originalWebTarget)
+ .request("application/json", "text/xml", "application/xml");
+ assertThat(invocationBuilder).isNotNull();
+ }
+
+ @Test
+ void request_MediaTypeAcceptedResponseTypes() {
+ var invocationBuilder = withWebTarget(originalWebTarget)
+ .request(MediaType.APPLICATION_JSON_TYPE, MediaType.TEXT_XML_TYPE, MediaType.APPLICATION_XML_TYPE);
+ assertThat(invocationBuilder).isNotNull();
+ }
+
+ @Test
+ void getConfiguration() {
+ var config = withWebTarget(originalWebTarget).getConfiguration();
+
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ }
+
+ @Test
+ void property() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .property("p1", "v1")
+ .property("p2", "v2")
+ .property("p3", "v3");
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.getProperty("p1")).isEqualTo("v1");
+ assertThat(config.getProperty("p2")).isEqualTo("v2");
+ assertThat(config.getProperty("p3")).isEqualTo("v3");
+ }
+
+ @Test
+ void register_ClassComponent() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(CsrfProtectionFilter.class)
+ .register(EncodingFeature.class);
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(CsrfProtectionFilter.class)).isTrue();
+ assertThat(config.isRegistered(EncodingFeature.class)).isTrue();
+ }
+
+ @Test
+ void register_ClassComponent_IntPriority() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(CsrfProtectionFilter.class, AUTHORIZATION)
+ .register(EncodingFeature.class, ENTITY_CODER);
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(CsrfProtectionFilter.class)).isTrue();
+ assertThat(config.isRegistered(EncodingFeature.class)).isTrue();
+ }
+
+ @Test
+ void register_ClassComponent_ClassVarArgContracts() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(NoOpClientLoggingFilter.class, ClientResponseFilter.class);
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(NoOpClientLoggingFilter.class)).isTrue();
+ }
+
+ @Test
+ void register_ClassComponent_MapContracts() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(NoOpClientLoggingFilter.class, Map.of(ClientResponseFilter.class, USER));
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(NoOpClientLoggingFilter.class)).isTrue();
+ }
+
+ @Test
+ void register_ObjectComponent() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(new CsrfProtectionFilter())
+ .register(new EncodingFeature());
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(CsrfProtectionFilter.class)).isTrue();
+ assertThat(config.isRegistered(EncodingFeature.class)).isTrue();
+ }
+
+ @Test
+ void register_ObjectComponent_IntPriority() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(new CsrfProtectionFilter(), AUTHORIZATION)
+ .register(new EncodingFeature(), ENTITY_CODER);
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(CsrfProtectionFilter.class)).isTrue();
+ assertThat(config.isRegistered(EncodingFeature.class)).isTrue();
+ }
+
+ @Test
+ void register_ObjectComponent_ClassVarArgContracts() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(new NoOpClientLoggingFilter(), ClientResponseFilter.class);
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(NoOpClientLoggingFilter.class)).isTrue();
+ }
+
+ @Test
+ void register_ObjectComponent_MapContracts() {
+ var originalWebTargetHelper = withWebTarget(originalWebTarget);
+ var newWebTarget = originalWebTargetHelper
+ .register(new NoOpClientLoggingFilter(), Map.of(ClientResponseFilter.class, USER));
+
+ assertThat(newWebTarget).isSameAs(originalWebTargetHelper);
+
+ var config = newWebTarget.getConfiguration();
+ assertThat(config).isSameAs(originalWebTarget.getConfiguration());
+ assertThat(config.isRegistered(NoOpClientLoggingFilter.class)).isTrue();
+ }
+ }
+
+ @Priority(USER)
+ public static class NoOpClientLoggingFilter implements ClientRequestFilter, ClientResponseFilter {
+
+ @Override
+ public void filter(ClientRequestContext requestContext) {
+ // no-op
+ }
+
+ @Override
+ public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) {
+ // no-op
+ }
+ }
+
private void assertIsOriginalWebTargetAndHasNoQuery(WebTargetHelper newWebTarget) {
assertThat(newWebTarget.wrapped()).isSameAs(originalWebTarget);
assertThat(newWebTarget.getUri()).hasNoQuery();