TaxonomyClient.java

package fr.tiogars.chat.client;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

/** Shared HTTP transport for reusable taxonomy CRUD clients. */
public abstract class TaxonomyClient<T, C, U> {
    private final URI baseUri;
    private final String path;
    private final Class<T> type;
    private final HttpClient httpClient;
    private final ObjectMapper mapper;

    protected TaxonomyClient(URI baseUri, String path, Class<T> type) {
        this(baseUri, path, type, HttpClient.newHttpClient(), new ObjectMapper().registerModule(new JavaTimeModule())
                .disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS));
    }

    protected TaxonomyClient(URI baseUri, String path, Class<T> type, HttpClient httpClient, ObjectMapper mapper) {
        this.baseUri = baseUri;
        this.path = path;
        this.type = type;
        this.httpClient = httpClient;
        this.mapper = mapper;
    }

    public T create(C request) throws IOException, InterruptedException {
        return mapper.readValue(send("", "POST", request, 201), type);
    }

    public T update(long id, U request) throws IOException, InterruptedException {
        return mapper.readValue(send("/" + id, "PUT", request, 200), type);
    }

    public T findById(long id) throws IOException, InterruptedException {
        return mapper.readValue(send("/" + id, "GET", null, 200), type);
    }

    public List<T> findAll() throws IOException, InterruptedException {
        return mapper.readValue(send("", "GET", null, 200), mapper.getTypeFactory().constructCollectionType(List.class, type));
    }

    public void delete(long id) throws IOException, InterruptedException {
        send("/" + id, "DELETE", null, 204);
    }

    private String send(String suffix, String method, Object body, int expectedStatus) throws IOException, InterruptedException {
        var publisher = body == null ? HttpRequest.BodyPublishers.noBody()
                : HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(body));
        var request = HttpRequest.newBuilder(baseUri.resolve(path + suffix)).header("Accept", "application/json")
                .header("Content-Type", "application/json").method(method, publisher).build();
        var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() != expectedStatus) {
            throw new TaskException("Expected HTTP " + expectedStatus + " but received " + response.statusCode());
        }
        return response.body();
    }
}