org.kiwiproject.jaxrs.client.WebTargetHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kiwi Show documentation
Show all versions of kiwi Show documentation
Kiwi is a utility library. We really like Google's Guava, and also use Apache Commons.
But if they don't have something we need, and we think it is useful, this is where we put it.
package org.kiwiproject.jaxrs.client;
import static java.util.Objects.isNull;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotBlank;
import static org.kiwiproject.base.KiwiPreconditions.checkArgumentNotNull;
import static org.kiwiproject.base.KiwiPreconditions.requireNotNull;
import static org.kiwiproject.collect.KiwiArrays.isNullOrEmpty;
import static org.kiwiproject.collect.KiwiLists.isNullOrEmpty;
import static org.kiwiproject.collect.KiwiMaps.isNullOrEmpty;
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.WebTarget;
import javax.ws.rs.core.MultivaluedMap;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
/**
* Use with JAX-RS {@link WebTarget} instances to provide convenient functionality when adding query parameters.
* Most of this functionality is intended for cases when you only want to add parameters when they are not null (or not
* blank in the case of Strings). If you want a query parameter to be added regardless of whether a value is present
* or not, use the regular {@link WebTarget#queryParam(String, Object...) queryParam} method in {@code WebTarget}.
*
* The methods provided by this helper class allow you to either require query parameters or include them only when
* they have a value. When you require a query parameter, an {@link IllegalArgumentException} is thrown when
* a caller does not supply a name or value. Other methods allow you to optionally include one or more query
* parameters, as well as add them from a {@link Map} or a {@link MultivaluedMap}, such that only non-null/non-blank
* values are added.
*
* Usage example (assuming {@link WebTargetClientHelper#withClient(Client) withClient} is statically imported):
*
* 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)
* .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:
*
* var pathResolvedTarget = client.target("/users/{userId}/trades/{tradeId}")
* .resolveTemplate("userId", userId)
* .resolveTemplate("tradeId", tradeId);
*
* var response = withWebTarget(pathResolvedTarget)
* .queryParamIfNotBlank("displayCurrency", currency)
* .queryParamIfNotNull("showLimitPrice", showLimitPrice)
* .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 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 {
@Delegate
private final WebTarget webTarget;
/**
* Package-private constructor. Used by {@link WebTargetClientHelper}.
*
* @param webTarget the WebTarget to wrap
*/
WebTargetHelper(WebTarget webTarget) {
this.webTarget = requireNotNull(webTarget, "webTarget must not be null");
}
/**
* @return the wrapped WebTarget
*/
@VisibleForTesting
WebTarget wrapped() {
return webTarget;
}
/**
* Create a new instance with the given {@link WebTarget}.
*
* @param webTarget the WebTarget to use
* @return a new instance
*/
public static WebTargetHelper withWebTarget(WebTarget webTarget) {
return new WebTargetHelper(webTarget);
}
/**
* Add the required query parameter.
*
* @param name the parameter name
* @param value the parameter value
* @return this instance
* @throws IllegalArgumentException if name is blank or value is null
*/
public WebTargetHelper queryParamRequireNotNull(String name, Object value) {
checkArgumentNotBlank(name, "name cannot be blank");
checkArgumentNotNull(value, "value cannot be null for parameter %s", name);
var newWebTarget = webTarget.queryParam(name, value);
return new WebTargetHelper(newWebTarget);
}
/**
* Add the given query parameter only if name is not blank and value is not null.
*
* @param name the parameter name
* @param value the parameter value
* @return this instance
*/
public WebTargetHelper queryParamIfNotNull(String name, Object value) {
if (isBlank(name) || isNull(value)) {
return this;
}
var newWebTarget = this.webTarget.queryParam(name, value);
return new WebTargetHelper(newWebTarget);
}
/**
* Adds any non-null values to the the given query parameter. If name is blank, this is a no-op.
*
* @param name the parameter name
* @param values one or more parameter values
* @return this instance
*/
public WebTargetHelper queryParamFilterNotNull(String name, Object... values) {
if (isBlank(name) || isNullOrEmpty(values)) {
return this;
}
return queryParamFilterNotNull(name, Arrays.stream(values));
}
/**
* Adds any non-null values to the the given query parameter. If name is blank, this is a no-op.
*
* @param name the parameter name
* @param values one or more parameter values
* @return this instance
*/
public WebTargetHelper queryParamFilterNotNull(String name, List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy