HttpSupport.java
package org.egothor.methodatlas.ai;
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.time.Duration;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Small HTTP utility component used by AI provider clients for outbound network
* communication and JSON processing support.
*
* <p>
* This class centralizes common HTTP-related functionality required by the AI
* provider integrations, including:
* </p>
* <ul>
* <li>creation of a configured {@link HttpClient}</li>
* <li>provision of a shared Jackson {@link ObjectMapper}</li>
* <li>execution of JSON-oriented HTTP requests</li>
* <li>construction of JSON {@code POST} requests</li>
* </ul>
*
* <p>
* The helper is intentionally lightweight and provider-agnostic. It does not
* implement provider-specific authentication, endpoint selection, or response
* normalization logic; those responsibilities remain in the concrete provider
* clients.
* </p>
*
* <p>
* The internally managed {@link ObjectMapper} is configured to ignore unknown
* JSON properties so that provider response deserialization remains resilient
* to non-breaking API changes.
* </p>
*
* <p>
* Instances of this class are immutable after construction.
* </p>
*
* @see HttpClient
* @see ObjectMapper
* @see AiProviderClient
*/
public final class HttpSupport {
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
/**
* Creates a new HTTP support helper with the specified connection timeout.
*
* <p>
* The supplied timeout is used as the connection timeout of the underlying
* {@link HttpClient}. Request-specific timeouts may still be configured
* independently on individual {@link HttpRequest} instances.
* </p>
*
* <p>
* The constructor also initializes a Jackson {@link ObjectMapper} configured
* with {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} disabled.
* </p>
*
* @param timeout connection timeout used for the underlying HTTP client
*/
public HttpSupport(Duration timeout) {
this.httpClient = HttpClient.newBuilder().connectTimeout(timeout).build();
this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
* Returns the configured HTTP client used by this helper.
*
* @return configured HTTP client instance
*/
public HttpClient httpClient() {
return httpClient;
}
/**
* Returns the configured Jackson object mapper used for JSON serialization and
* deserialization.
*
* @return configured object mapper instance
*/
public ObjectMapper objectMapper() {
return objectMapper;
}
/**
* Executes an HTTP request expected to return a JSON response body and returns
* the response content as text.
*
* <p>
* The method sends the supplied request using the internally configured
* {@link HttpClient}. Responses with HTTP status codes outside the successful
* {@code 2xx} range are treated as failures and cause an {@link IOException} to
* be thrown containing both the status code and response body.
* </p>
*
* <p>
* Despite the method name, the request itself is not required to be a
* {@code POST} request; the method simply executes the provided request and
* validates that the response indicates success.
* </p>
*
* @param request HTTP request to execute
* @return response body as text
*
* @throws IOException if request execution fails or if the HTTP
* response status code is outside the successful
* {@code 2xx} range
* @throws InterruptedException if the calling thread is interrupted while
* waiting for the response
*/
public String postJson(HttpRequest request) throws IOException, InterruptedException {
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode();
if (statusCode < 200 || statusCode >= 300) {
throw new IOException("HTTP " + statusCode + ": " + response.body());
}
return response.body();
}
/**
* Creates a JSON-oriented HTTP {@code POST} request builder.
*
* <p>
* The returned builder is preconfigured with:
* </p>
* <ul>
* <li>the supplied target {@link URI}</li>
* <li>the supplied request timeout</li>
* <li>{@code Content-Type: application/json}</li>
* <li>a {@code POST} request body containing the supplied JSON text</li>
* </ul>
*
* <p>
* Callers may further customize the returned builder, for example by adding
* authentication or provider-specific headers, before invoking
* {@link HttpRequest.Builder#build()}.
* </p>
*
* @param uri target URI of the request
* @param body serialized JSON request body
* @param timeout request timeout
* @return preconfigured HTTP request builder for a JSON {@code POST} request
*/
public HttpRequest.Builder jsonPost(URI uri, String body, Duration timeout) {
return HttpRequest.newBuilder(uri).timeout(timeout).header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body));
}
}