...
{
"equals": {
"field": "field",
"value": "value"
}
}
"Greater Than" Filter
The value of the field must be greater than the one provided.
var filter = GreaterThanFilter.builder().field("field").value(1).build();
{
"gt": {
"field": "field",
"value": 1
}
}
"Greater Than or Equals" Filter
The value of the field must be greater than or equals to the one provided.
var filter = GreaterThanOrEqualsFilter.builder().field("field").value(1).build();
{
"gte": {
"field": "field",
"value": 1
}
}
"Less Than" Filter
The value of the field must be less than the one provided.
var filter = LessThanFilter.builder().field("field").value(1).build();
{
"lt": {
"field": "field",
"value": 1
}
}
"Less Than or Equals" Filter
The value of the field must be less than or equals to the one provided.
var filter = LessThanOrEqualsFilter.builder().field("field").value(1).build();
{
"lte": {
"field": "field",
"value": 1
}
}
"In" Filter
The value of the field must be one of the provided values.
var filter = InFilter.builder().field("field").value("valueA").value("valueB").build();
{
"in": {
"field": "field",
"values": [
"valueA",
"valueB"
]
}
}
"Empty" Filter
Checks if a field is empty:
- For a collection,
true
if its size is 0 - For a String,
true
if its length is 0 - For any other type,
true
if it isnull
var filter = EmptyFilter.builder()
.field("field")
.build();
{
"empty": {
"field": "field"
}
}
"Exists" Filter
Checks if a field exists.
var filter = ExistsFilter.builder()
.field("field")
.build();
{
"exists": {
"field": "field"
}
}
"Not" Filter
Negates the inner clause.
var filter = NotFilter.builder()
.clause(EqualsFilter.builder().field("field").value("value").build())
.build();
{
"not": {
"equals": {
"field": "field",
"value": "value"
}
}
}
"Null" Filter
Checks if a field is null. Note that while the "exists" filter checks whether the field is present or not, the "null" filter expects the field to be present but with null
value.
var filter = NullFilter.builder()
.field("field")
.build();
{
"null": {
"field": "field"
}
}
Boolean Operators
"And" Filter
All conditions in the list must be evaluated to true
.
var filter = AndFilter.builder()
.clause(EqualsFilter.builder().field("fieldA").value("valueA").build())
.clause(EqualsFilter.builder().field("fieldB").value("valueB").build())
.build();
{
"and": [
{
"equals": {
"field": "fieldA",
"value": "valueA"
}
}, {
"equals": {
"field": "fieldB",
"value": "valueB"
}
}
]
}
"Or" Filter
At least one condition in the list must be evaluated to true
.
var filter = OrFilter.builder()
.clause(EqualsFilter.builder().field("fieldA").value("valueA").build())
.clause(EqualsFilter.builder().field("fieldB").value("valueB").build())
.build();
{
"or": [
{
"equals": {
"field": "fieldA",
"value": "valueA"
}
}, {
"equals": {
"field": "fieldB",
"value": "valueB"
}
}
]
}
HTTP Base Client
The HTTP Base Client is an abstraction of the OkHttpClient. It provides features such as retry handlers and automated paginated requests.
The minimum initialization for the client defines the configuration of the HTTP connections and other shared properties to be used by the endpoints:
public class MyClient extends HttpBaseClient {
protected MyClient(
@NonNull OkHttpClient client,
@NonNull ObjectMapper objectMapper,
@NonNull BackoffPolicyProperties backoffPolicy,
MeterRegistry meterRegistry,
@NonNull ScrollProperties scroll)
{
super(client, objectMapper, backoffPolicy, scroll);
}
@Override
public boolean ping() {
return true;
}
public static Builder builder() {
return new Builder();
}
public static final class Builder extends HttpBaseClientBuilder<Builder, MyClient> {
@Override
public MyClient build() {
return new MyClient(
newClientBuilder().build(),
getObjectMapper(),
getBackoffPolicy(),
getScroll());
}
}
}
The builder can be also initialized with a HttpBaseClientProperties
instance:
public class MyClient extends HttpBaseClient {
...
public static Builder builder(HttpBaseClientProperties clientProperties) {
return new Builder(clientProperties);
}
@NoArgsConstructor
public static final class Builder extends HttpBaseClientBuilder<Builder, MyClient> {
protected Builder(HttpBaseClientProperties clientProperties) {
super(clientProperties);
}
...
}
}
If some configurations are not desired on the implemented client, they can be disabled in the builder:
public class MyClient extends HttpBaseClient {
...
@HttpBaseClientBuilder.DisabledConfig({
HttpBaseClientBuilder.Config.COMPRESS_REQUESTS,
HttpBaseClientBuilder.Config.FOLLOW_REDIRECTS,
HttpBaseClientBuilder.Config.SCROLL
})
public static final class Builder extends HttpBaseClientBuilder<Builder, MyClient> {
...
}
}
any attempt to set a disabled configuration will throw an UnsupportedOperationException
.
By default, the retry of the executeWithBackoff(...)
method will happen if the HTTP response code is:
- 408 - Request Time Out
- 429 - Too Many Request
- 500 - Internal Server Error
- 504 - Gateway Timeout
The condition can be changed by sending a different predicate to the method:
client.executeWithBackoff(requestDetails, response -> false);
In this case, the request will not be retried because of a status code (although it might be retried because of an exception during the execution).
HTTP Round Robin Client
The HTTP Round Robin Client is a specialization of the HttpBaseClient
backed up by a round-robin collection. Provides the corresponding classes as described in the previous section: HttpRoundRobinClient
, HttpRoundRobinClientBuilder
, and HttpRoundRobinClientProperties
.
HTTP Client Request
The request object is a simple POJO with the properties needed to execute the HTTP call:
@RequiredArgsConstructor
public class MyRequest implements HttpClientRequest {
private final String id;
}
For cases when no data is needed in the request, an empty implementation is avaliable in
HttpRoundRobinClient#EMPTY_REQUEST
HTTP Client Pageable Request
The HttpClientPageableRequest
interface is an extension of HttpClientRequest
and defines default methods to handle pagination parameters.
HTTP Client Response
Single Response
Expected when the response is a single payload.
public class MySinglePayloadResponse extends AbstractSingleResponse<MyRequest> {
public MySinglePayloadResponse(
ObjectMapper objectMapper,
HttpClientRequestDetails<MyRequest> requestDetails,
Response httpResponse
) {
super(objectMapper, requestDetails, httpResponse);
}
@Override
protected void handleSuccess(Response httpResponse) {
// Do something with the response
}
}
With this definition, the endpoint can be exposed in the client:
public class MyClient extends HttpRoundRobinClient {
...
public MySinglePayloadResponse get(MyRequest clientRequest) {
var url = serverHosts.next()
.newBuilder()
.addPathSegment("content")
.addPathSegment(request.getId())
.build();
var requestDetails = new HttpClientRequestDetails<>(
new Request.Builder().url(url).get().build(),
clientRequest);
return new MySinglePayloadResponse(
objectMapper,
requestDetails,
executeWithBackoff(requestDetails)
);
}
...
}
Both handleSuccess(Response)
and handleFailure(Response)
have a default implementation that can be overridden. A response is considered a success if its HTTP status code is in the 200 to 299 range and its body is not null
(not to be confused with an empty body). This check can be changed in the constructor:
public class MySinglePayloadResponse extends AbstractSingleResponse<MyRequest> {
public MySinglePayloadResponse(
ObjectMapper objectMapper,
HttpClientRequestDetails<MyRequest> requestDetails,
Response httpResponse
) {
super(objectMapper, requestDetails, httpResponse, r -> r.code() == 400);
}
}
If the response was a failure, but it had a payload, it can be retrieved with AbstractSingleResponse#getErrorMessage()
, or AbstractSingleResponse#getErrorMessage(Class)
(to convert the payload into the given class type).
Iterable Response
Expected when the response is a full collection of iterable elements with no pagination. It is assumed that the payload of the response is a JSON array (if that's not the case, the handleSuccess(Response httpResponse)
method must be overridden).
public class MyIterableResponse extends AbstractIterableResponse<MyRequest, String> {
public MyIterableResponse(
ObjectMapper objectMapper,
HttpClientRequestDetails<MyRequest> requestDetails,
Response httpResponse
) {
super(objectMapper, requestDetails, httpResponse);
}
@Override
protected String parseElement(JsonNode jsonNode) {
return jsonNode.asText();
}
}
With this definition, the endpoint can be exposed in the client:
public class MyClient extends HttpRoundRobinClient {
...
public MyIterableResponse list(MyRequest clientRequest) {
var url = serverHosts.next()
.newBuilder()
.addPathSegment("list")
.addPathSegment(request.getId())
.build();
var requestDetails = new HttpClientRequestDetails<>(
new Request.Builder().url(url).get().build(),
clientRequest);
return new MyIterableResponse(
objectMapper,
requestDetails,
executeWithBackoff(requestDetails)
);
}
...
}
Each element in the response can then be retrieved by using the Iterable<>
interface.
The predicate to determine if a response was successful can be provided in the constructor, the same way as in the previous sections.
Paginated Response
Token Page
Expected when the response is a consecutive list of pages obtained with a token.
Token Page Response
The response corresponds to each page from the pagination requests. Since it is not possible to automatically determine where is the token for the next page, the AbstractTokenPageResponse#handleSuccess(Response)
method must be implemented.
public class MyTokenPageResponse extends AbstractTokenPageResponse<MyRequest, String> {
public MyTokenPageResponse(
ObjectMapper objectMapper,
HttpClientRequestDetails<MyRequest> requestDetails,
Response httpResponse,
String currentToken
) {
super(objectMapper, requestDetails, httpResponse, currentToken);
}
@Override
protected void handleSuccess(Response response) {
try {
var json = objectMapper.readTree(Objects.requireNonNull(response.body()).byteStream());
this.nextPageReference = json.get("token").asText();
if (json.has("content")) {
this.elements = new JsonLazyIterator<>(json.get("content"), this::parseElement);
} else {
this.elements = Collections.emptyIterator();
}
} catch (Exception ex) {
throw new DataException(ex.getMessage(), ex);
}
}
@Override
protected String parseElement(JsonNode jsonNode) {
return jsonNode.asText();
}
}
The predicate to determine if a response was successful can be provided in the constructor, the same way as in the previous sections.
Token Page Collection
The response corresponds to a collection of all the page responses that iterates through the data.
public class MyTokenPageCollectionResponse extends AbstractTokenCollectionResponse<MyRequest, String, MyTokenPageResponse> {
public MyTokenPageCollectionResponse(Function<String, MyTokenPageResponse> pageFunction) {
super(pageFunction);
}
}