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();