ApiClientModule.java
package io.github.evisentin.wordpress.rest.client.adapter.okhttp.modules;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.evisentin.wordpress.rest.client.domain.model.WpPagedResponse;
import io.github.evisentin.wordpress.rest.client.domain.model.query.WpPaginationQuery;
import lombok.RequiredArgsConstructor;
import lombok.val;
import okhttp3.*;
import org.apache.commons.text.StringSubstitutor;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static io.github.evisentin.wordpress.rest.client.adapter.okhttp.http.HttpHeaders.ACCEPT;
import static io.github.evisentin.wordpress.rest.client.adapter.okhttp.http.HttpHeaders.X_WP_TOTAL;
import static io.github.evisentin.wordpress.rest.client.adapter.okhttp.http.HttpHeaders.X_WP_TOTAL_PAGES;
import static io.github.evisentin.wordpress.rest.client.adapter.okhttp.http.MediaTypes.MEDIA_TYPE_APPLICATION_JSON;
import static io.github.evisentin.wordpress.rest.client.adapter.okhttp.http.MimeTypes.APPLICATION_JSON;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
/**
* Base class for OkHttp-backed WordPress API modules.
*
* <p>Provides shared functionality for building endpoint URLs, executing HTTP requests, serializing request bodies,
* deserializing responses, handling paginated responses, and submitting multipart requests.</p>
*
* <p>Concrete modules extend this class to implement resource-specific WordPress REST API operations.</p>
*/
@RequiredArgsConstructor
abstract class ApiClientModule {
protected static final String API_URL = "apiUrl";
protected final String apiUrl;
protected final OkHttpClient httpClient;
protected final ObjectMapper mapper;
protected <T> T performDeleteRequest(final HttpUrl.Builder urlBuilder,
final TypeReference<T> responseType) throws IOException {
final Request request = new Request.Builder()
.url(urlBuilder.build())
.header(ACCEPT, APPLICATION_JSON)
.delete()
.build();
try (Response response = httpClient.newCall(request).execute()) {
final ResponseBody body = response.body();
return mapper.readValue(body.string(), responseType);
}
}
protected <T> T performGetRequest(final HttpUrl.Builder urlBuilder,
final TypeReference<T> responseType) throws IOException {
final Request request = new Request.Builder()
.url(urlBuilder.build())
.header(ACCEPT, APPLICATION_JSON)
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
final ResponseBody body = response.body();
return mapper.readValue(body.string(), responseType);
}
}
protected <T> WpPagedResponse<T> performPagingRequest(final HttpUrl.Builder urlBuilder,
final WpPaginationQuery paginationQuery,
final TypeReference<List<T>> responseType) throws IOException {
final Request request = new Request.Builder()
.url(urlBuilder.build())
.header(ACCEPT, APPLICATION_JSON)
.get()
.build();
try (Response response = httpClient.newCall(request).execute()) {
final int totalItems = Integer.parseInt(requireNonNull(response.header(X_WP_TOTAL, "0")));
final int totalPages = Integer.parseInt(requireNonNull(response.header(X_WP_TOTAL_PAGES, "0")));
final String json = response.body().string();
final List<T> items = mapper.readValue(json, responseType);
return new WpPagedResponse<>(
items,
paginationQuery.pageSize(),
totalItems,
totalPages,
paginationQuery.pageNumber()
);
}
}
protected <T> T performPostWithBody(final HttpUrl.Builder urlBuilder,
final Object requestBody,
final TypeReference<T> responseType) throws IOException {
Request request = new Request.Builder()
.url(urlBuilder.build())
.header(ACCEPT, APPLICATION_JSON)
.post(RequestBody.create(toJson(requestBody), MEDIA_TYPE_APPLICATION_JSON))
.build();
try (Response response = httpClient.newCall(request).execute()) {
val json = response.body().string();
return mapper.readValue(json, responseType);
}
}
protected <T> T performPostWithMultiPartBody(final HttpUrl.Builder urlBuilder,
final MultipartBody requestBody,
final TypeReference<T> responseType) throws IOException {
Request request = new Request.Builder()
.url(urlBuilder.build())
.header(ACCEPT, APPLICATION_JSON)
.post(requestBody)
.build();
try (Response response = httpClient.newCall(request).execute()) {
val json = response.body().string();
return mapper.readValue(json, responseType);
}
}
protected String toJson(final Object object) throws JsonProcessingException {
return mapper.writeValueAsString(object);
}
protected HttpUrl.Builder urlBuilder(final String path, final Map<String, Object> pathParams) {
final String substituted = new StringSubstitutor(emptyIfNull(pathParams)).replace(path);
return Objects.requireNonNull(HttpUrl.parse(substituted)).newBuilder();
}
protected static <K, V> Map<K, V> emptyIfNull(final Map<K, V> map) {
return ofNullable(map).orElseGet(Collections::emptyMap);
}
}