goldenChat base source add
This commit is contained in:
+430
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.datasources.http;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* Abstract base class for HTTP-based BarSeries data sources.
|
||||
* <p>
|
||||
* This class provides common infrastructure for HTTP-based data sources,
|
||||
* including:
|
||||
* <ul>
|
||||
* <li>HTTP client management via {@link HttpClientWrapper}</li>
|
||||
* <li>Response caching to disk for faster subsequent requests</li>
|
||||
* <li>Cache validation and management</li>
|
||||
* <li>Common cache file operations</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Subclasses should implement the API-specific logic such as:
|
||||
* <ul>
|
||||
* <li>Building API-specific URLs and request parameters</li>
|
||||
* <li>Parsing API-specific response formats</li>
|
||||
* <li>Defining interval enums and their API values</li>
|
||||
* <li>Implementing interval-specific timestamp truncation logic</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* <strong>Example usage:</strong>
|
||||
*
|
||||
* <pre>
|
||||
* public class MyHttpDataSource extends AbstractHttpBarSeriesDataSource {
|
||||
* public MyHttpDataSource() {
|
||||
* super(new DefaultHttpClientWrapper(), false);
|
||||
* }
|
||||
*
|
||||
* // Implement abstract methods and API-specific logic
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @since 0.20
|
||||
*/
|
||||
public abstract class AbstractHttpBarSeriesDataSource implements HttpBarSeriesDataSource {
|
||||
|
||||
/**
|
||||
* Default directory for caching HTTP responses.
|
||||
*/
|
||||
public static final String DEFAULT_RESPONSE_CACHE_DIR = "temp/responses";
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(AbstractHttpBarSeriesDataSource.class);
|
||||
|
||||
protected final HttpClientWrapper httpClient;
|
||||
protected final boolean enableResponseCaching;
|
||||
protected final String responseCacheDir;
|
||||
|
||||
/**
|
||||
* Creates a new AbstractHttpBarSeriesDataSource with the specified
|
||||
* HttpClientWrapper and caching option, using the default cache directory.
|
||||
*
|
||||
* @param httpClient the HttpClientWrapper to use for API requests
|
||||
* (can be a mock for testing)
|
||||
* @param enableResponseCaching if true, responses will be cached to disk for
|
||||
* faster subsequent requests
|
||||
* @throws IllegalArgumentException if httpClient is null
|
||||
*/
|
||||
protected AbstractHttpBarSeriesDataSource(HttpClientWrapper httpClient, boolean enableResponseCaching) {
|
||||
if (httpClient == null) {
|
||||
throw new IllegalArgumentException("HttpClientWrapper cannot be null");
|
||||
}
|
||||
this.httpClient = httpClient;
|
||||
this.enableResponseCaching = enableResponseCaching;
|
||||
this.responseCacheDir = DEFAULT_RESPONSE_CACHE_DIR;
|
||||
if (enableResponseCaching) {
|
||||
ensureCacheDirectoryExists();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AbstractHttpBarSeriesDataSource with the specified
|
||||
* HttpClientWrapper and custom cache directory. Response caching is
|
||||
* automatically enabled when a cache directory is specified.
|
||||
*
|
||||
* @param httpClient the HttpClientWrapper to use for API requests (can be
|
||||
* a mock for testing)
|
||||
* @param responseCacheDir the directory path for caching responses (can be
|
||||
* relative or absolute)
|
||||
* @throws IllegalArgumentException if httpClient is null or responseCacheDir is
|
||||
* null or empty
|
||||
*/
|
||||
protected AbstractHttpBarSeriesDataSource(HttpClientWrapper httpClient, String responseCacheDir) {
|
||||
if (httpClient == null) {
|
||||
throw new IllegalArgumentException("HttpClientWrapper cannot be null");
|
||||
}
|
||||
if (responseCacheDir == null || responseCacheDir.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Response cache directory cannot be null or empty");
|
||||
}
|
||||
this.httpClient = httpClient;
|
||||
this.enableResponseCaching = true;
|
||||
this.responseCacheDir = responseCacheDir.trim();
|
||||
ensureCacheDirectoryExists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AbstractHttpBarSeriesDataSource with the specified HttpClient.
|
||||
* This is a convenience constructor that wraps the HttpClient in a
|
||||
* DefaultHttpClientWrapper, using the default cache directory.
|
||||
*
|
||||
* @param httpClient the HttpClient to use for API requests
|
||||
* @param enableResponseCaching if true, responses will be cached to disk for
|
||||
* faster subsequent requests
|
||||
*/
|
||||
protected AbstractHttpBarSeriesDataSource(HttpClient httpClient, boolean enableResponseCaching) {
|
||||
this(new DefaultHttpClientWrapper(httpClient), enableResponseCaching);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AbstractHttpBarSeriesDataSource with the specified HttpClient
|
||||
* and custom cache directory. This is a convenience constructor that wraps the
|
||||
* HttpClient in a DefaultHttpClientWrapper. Response caching is automatically
|
||||
* enabled when a cache directory is specified.
|
||||
*
|
||||
* @param httpClient the HttpClient to use for API requests
|
||||
* @param responseCacheDir the directory path for caching responses (can be
|
||||
* relative or absolute)
|
||||
*/
|
||||
protected AbstractHttpBarSeriesDataSource(HttpClient httpClient, String responseCacheDir) {
|
||||
this(new DefaultHttpClientWrapper(httpClient), responseCacheDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClientWrapper getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResponseCachingEnabled() {
|
||||
return enableResponseCaching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response cache directory path.
|
||||
*
|
||||
* @return the cache directory path
|
||||
*/
|
||||
public String getResponseCacheDir() {
|
||||
return responseCacheDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the cache directory exists, creating it if necessary.
|
||||
*/
|
||||
protected void ensureCacheDirectoryExists() {
|
||||
try {
|
||||
Path cacheDir = Paths.get(responseCacheDir);
|
||||
if (!Files.exists(cacheDir)) {
|
||||
Files.createDirectories(cacheDir);
|
||||
LOG.debug("Created cache directory: {}", cacheDir.toAbsolutePath());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to create cache directory: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates a timestamp based on the interval duration to enable cache hits for
|
||||
* requests within the same time period. This is a generic implementation that
|
||||
* works with Duration. Subclasses can override this method to provide more
|
||||
* precise truncation logic for specific interval types (e.g., week boundaries,
|
||||
* month boundaries).
|
||||
* <p>
|
||||
* For example, for 1-day intervals, timestamps are truncated to the start of
|
||||
* the day. For 15-minute intervals, timestamps are truncated to the start of
|
||||
* the 15-minute period.
|
||||
*
|
||||
* @param instant the timestamp to truncate
|
||||
* @param interval the interval duration to use for truncation
|
||||
* @return the truncated timestamp
|
||||
*/
|
||||
protected Instant truncateTimestampForCache(Instant instant, Duration interval) {
|
||||
ZonedDateTime zdt = instant.atZone(ZoneOffset.UTC);
|
||||
ZonedDateTime truncated;
|
||||
|
||||
long seconds = interval.getSeconds();
|
||||
long minutes = seconds / 60;
|
||||
long hours = minutes / 60;
|
||||
long days = hours / 24;
|
||||
|
||||
if (days >= 30) {
|
||||
// For monthly intervals (approximately 30 days), truncate to start of month
|
||||
truncated = zdt.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
} else if (days >= 7) {
|
||||
// For weekly intervals (approximately 7 days), truncate to start of week
|
||||
// (Monday)
|
||||
int daysFromMonday = zdt.getDayOfWeek().getValue() - java.time.DayOfWeek.MONDAY.getValue();
|
||||
if (daysFromMonday < 0) {
|
||||
daysFromMonday += 7; // Handle Sunday (value 7)
|
||||
}
|
||||
truncated = zdt.minusDays(daysFromMonday).withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
} else if (days >= 1) {
|
||||
// For daily intervals, truncate to start of day
|
||||
truncated = zdt.withHour(0).withMinute(0).withSecond(0).withNano(0);
|
||||
} else if (hours >= 1) {
|
||||
// For hourly intervals, truncate to start of hour period
|
||||
int hour = (int) ((zdt.getHour() / hours) * hours);
|
||||
truncated = zdt.withHour(hour).withMinute(0).withSecond(0).withNano(0);
|
||||
} else if (minutes >= 1) {
|
||||
// For minute intervals, truncate to start of minute period
|
||||
int minute = (int) ((zdt.getMinute() / minutes) * minutes);
|
||||
truncated = zdt.withMinute(minute).withSecond(0).withNano(0);
|
||||
} else {
|
||||
// For second-level intervals, truncate to start of second
|
||||
int second = (int) ((zdt.getSecond() / seconds) * seconds);
|
||||
truncated = zdt.withSecond(second).withNano(0);
|
||||
}
|
||||
|
||||
return truncated.toInstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the cache file path for a given request.
|
||||
*
|
||||
* @param symbol the symbol (ticker, product ID, etc.)
|
||||
* @param startDateTime the start date/time (will be truncated)
|
||||
* @param endDateTime the end date/time (will be truncated)
|
||||
* @param interval the interval duration (used for truncation and filename)
|
||||
* @param notes optional notes section to append to filename (can be
|
||||
* null or empty)
|
||||
* @return the cache file path
|
||||
*/
|
||||
protected Path getCacheFilePath(String symbol, Instant startDateTime, Instant endDateTime, Duration interval,
|
||||
String notes) {
|
||||
Instant truncatedStart = truncateTimestampForCache(startDateTime, interval);
|
||||
Instant truncatedEnd = truncateTimestampForCache(endDateTime, interval);
|
||||
|
||||
String sourcePrefix = getSourceName().isEmpty() ? "" : getSourceName() + "-";
|
||||
String notesSuffix = (notes != null && !notes.trim().isEmpty()) ? "_" + notes.trim() : "";
|
||||
// Use Duration.toString() for standardized format (e.g., "PT24H" for 1 day,
|
||||
// "PT1H" for 1 hour)
|
||||
String durationString = interval.toString();
|
||||
String filename = String.format("%s%s-%s-%d-%d%s.json", sourcePrefix,
|
||||
symbol.toUpperCase().replaceAll("[^A-Z0-9-]", "_"), durationString, truncatedStart.getEpochSecond(),
|
||||
truncatedEnd.getEpochSecond(), notesSuffix);
|
||||
|
||||
return Paths.get(responseCacheDir, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a cache file exists and is still valid based on the interval. For
|
||||
* historical data (end date in the past), cache is valid indefinitely. For
|
||||
* current data (end date is recent), cache expires after the interval duration.
|
||||
* <p>
|
||||
* Cache validity is determined by the file's modification time (when the cache
|
||||
* file was created), not access time. This is appropriate because we want to
|
||||
* know how old the cached data is, not when it was last read.
|
||||
*
|
||||
* @param cacheFile the cache file path
|
||||
* @param interval the interval duration
|
||||
* @param endDateTime the end date/time of the request
|
||||
* @return true if cache is valid, false otherwise
|
||||
*/
|
||||
protected boolean isCacheValid(Path cacheFile, Duration interval, Instant endDateTime) {
|
||||
if (!Files.exists(cacheFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check file modification time (when the cache file was created/written)
|
||||
Instant fileModified = Files.getLastModifiedTime(cacheFile).toInstant();
|
||||
Instant now = Instant.now();
|
||||
|
||||
// If the request is for historical data (end date is in the past), cache is
|
||||
// valid indefinitely
|
||||
if (endDateTime.isBefore(now.minus(interval))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For current/recent data, cache expires after the interval duration
|
||||
Duration age = Duration.between(fileModified, now);
|
||||
return age.compareTo(interval) < 0;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to check cache file validity: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a cached response from disk.
|
||||
*
|
||||
* @param cacheFile the cache file path
|
||||
* @return the cached JSON response, or null if read fails
|
||||
*/
|
||||
protected String readFromCache(Path cacheFile) {
|
||||
try {
|
||||
String content = Files.readString(cacheFile, StandardCharsets.UTF_8);
|
||||
LOG.debug("Cache hit: {}", cacheFile.getFileName());
|
||||
return content;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to read from cache: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a response to the cache.
|
||||
*
|
||||
* @param cacheFile the cache file path
|
||||
* @param response the JSON response to cache
|
||||
*/
|
||||
protected void writeToCache(Path cacheFile, String response) {
|
||||
try {
|
||||
// Ensure parent directory exists
|
||||
Path parentDir = cacheFile.getParent();
|
||||
if (parentDir != null && !Files.exists(parentDir)) {
|
||||
Files.createDirectories(parentDir);
|
||||
}
|
||||
Files.writeString(cacheFile, response, StandardCharsets.UTF_8);
|
||||
LOG.debug("Cached response: {}", cacheFile.getFileName());
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to write to cache: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all cache files in the cache directory that belong to this data
|
||||
* source. Files are identified by the source name prefix (e.g., "Coinbase-",
|
||||
* "YahooFinance-").
|
||||
*
|
||||
* @return the number of files deleted
|
||||
*/
|
||||
public int deleteAllCacheFiles() {
|
||||
return deleteCacheFilesOlderThan(Duration.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes cache files that are older than the specified maximum age. Files are
|
||||
* identified by the source name prefix (e.g., "Coinbase-", "YahooFinance-") and
|
||||
* are deleted based on their file modification time (when the cache file was
|
||||
* created/written).
|
||||
* <p>
|
||||
* <strong>Note:</strong> This method uses file modification time, not access
|
||||
* time. Modification time represents when the cache file was created (when the
|
||||
* response was cached), which is appropriate for determining cache age. Access
|
||||
* time (when the file was last read) is not used because it's not reliably
|
||||
* available across all platforms and filesystems.
|
||||
*
|
||||
* @param maxAge the maximum age of files to keep (files older than this will be
|
||||
* deleted). Use {@link Duration#ZERO} to delete all files.
|
||||
* @return the number of files deleted
|
||||
*/
|
||||
public int deleteCacheFilesOlderThan(Duration maxAge) {
|
||||
if (!Files.exists(Paths.get(responseCacheDir))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String sourcePrefix = getSourceName().isEmpty() ? "" : getSourceName() + "-";
|
||||
int deletedCount = 0;
|
||||
Instant cutoffTime = Instant.now().minus(maxAge);
|
||||
|
||||
try {
|
||||
Path cacheDir = Paths.get(responseCacheDir);
|
||||
for (Path path : Files.list(cacheDir).toList()) {
|
||||
if (Files.isRegularFile(path)) {
|
||||
String filename = path.getFileName().toString();
|
||||
// Check if file belongs to this data source
|
||||
if (sourcePrefix.isEmpty() || filename.startsWith(sourcePrefix)) {
|
||||
try {
|
||||
// If maxAge is ZERO, delete all files regardless of age
|
||||
if (maxAge.isZero() || Files.getLastModifiedTime(path).toInstant().isBefore(cutoffTime)) {
|
||||
Files.delete(path);
|
||||
deletedCount++;
|
||||
LOG.debug("Deleted cache file: {}", filename);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to delete cache file {}: {}", filename, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to list cache directory: {}", e.getMessage());
|
||||
return deletedCount;
|
||||
}
|
||||
|
||||
if (deletedCount > 0) {
|
||||
LOG.info("Deleted {} stale cache file(s) with prefix '{}'", deletedCount, sourcePrefix);
|
||||
}
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes stale cache files. A file is considered stale if it's older than 30
|
||||
* days and the end timestamp encoded in the filename indicates it's for
|
||||
* historical data (data that won't change). This is a conservative approach
|
||||
* that preserves recent caches and historical data caches that might still be
|
||||
* useful.
|
||||
* <p>
|
||||
* For more control, use {@link #deleteCacheFilesOlderThan(Duration)} or
|
||||
* {@link #deleteAllCacheFiles()}.
|
||||
*
|
||||
* @return the number of files deleted
|
||||
*/
|
||||
public int deleteStaleCacheFiles() {
|
||||
return deleteStaleCacheFiles(Duration.ofDays(30));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes stale cache files older than the specified age. A file is considered
|
||||
* stale if it's older than the specified age. Files are identified by the
|
||||
* source name prefix (e.g., "Coinbase-", "YahooFinance-").
|
||||
*
|
||||
* @param maxAge the maximum age of files to keep (files older than this will be
|
||||
* deleted)
|
||||
* @return the number of files deleted
|
||||
*/
|
||||
public int deleteStaleCacheFiles(Duration maxAge) {
|
||||
return deleteCacheFilesOlderThan(maxAge);
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.datasources.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link HttpClientWrapper} that delegates to a
|
||||
* {@link HttpClient}.
|
||||
*/
|
||||
public class DefaultHttpClientWrapper implements HttpClientWrapper {
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/**
|
||||
* Creates a new wrapper around the specified HttpClient.
|
||||
*
|
||||
* @param httpClient the HttpClient to wrap
|
||||
*/
|
||||
public DefaultHttpClientWrapper(HttpClient httpClient) {
|
||||
if (httpClient == null) {
|
||||
throw new IllegalArgumentException("HttpClient cannot be null");
|
||||
}
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new wrapper with a default HttpClient.
|
||||
*/
|
||||
public DefaultHttpClientWrapper() {
|
||||
this(HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> HttpResponseWrapper<T> send(HttpRequest request, HttpResponse.BodyHandler<T> handler)
|
||||
throws IOException, InterruptedException {
|
||||
HttpResponse<T> response = httpClient.send(request, handler);
|
||||
return new DefaultHttpResponseWrapper<>(response);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.datasources.http;
|
||||
|
||||
import java.net.http.HttpResponse;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link HttpResponseWrapper} that delegates to a
|
||||
* {@link HttpResponse}.
|
||||
*
|
||||
* @param <T> the response body type
|
||||
*/
|
||||
public class DefaultHttpResponseWrapper<T> implements HttpResponseWrapper<T> {
|
||||
|
||||
private final HttpResponse<T> httpResponse;
|
||||
|
||||
/**
|
||||
* Creates a new wrapper around the specified HttpResponse.
|
||||
*
|
||||
* @param httpResponse the HttpResponse to wrap
|
||||
*/
|
||||
public DefaultHttpResponseWrapper(HttpResponse<T> httpResponse) {
|
||||
if (httpResponse == null) {
|
||||
throw new IllegalArgumentException("HttpResponse cannot be null");
|
||||
}
|
||||
this.httpResponse = httpResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int statusCode() {
|
||||
return httpResponse.statusCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T body() {
|
||||
return httpResponse.body();
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.datasources.http;
|
||||
|
||||
import ta4jexamples.datasources.BarSeriesDataSource;
|
||||
|
||||
/**
|
||||
* Marker interface for HTTP-based BarSeries data sources.
|
||||
* <p>
|
||||
* This interface extends {@link BarSeriesDataSource} to identify data sources
|
||||
* that fetch data over HTTP. Implementations of this interface typically use
|
||||
* {@link HttpClientWrapper} for making HTTP requests and may support response
|
||||
* caching.
|
||||
* <p>
|
||||
* HTTP-based data sources share common functionality such as:
|
||||
* <ul>
|
||||
* <li>HTTP request handling via {@link HttpClientWrapper}</li>
|
||||
* <li>Response caching to disk for faster subsequent requests</li>
|
||||
* <li>Cache validation and management</li>
|
||||
* <li>Pagination for large data requests</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* For shared implementation, consider extending
|
||||
* {@link AbstractHttpBarSeriesDataSource} which provides common HTTP and
|
||||
* caching infrastructure.
|
||||
*
|
||||
* @since 0.20
|
||||
*/
|
||||
public interface HttpBarSeriesDataSource extends BarSeriesDataSource {
|
||||
|
||||
/**
|
||||
* Returns the HttpClientWrapper used by this data source for making HTTP
|
||||
* requests.
|
||||
*
|
||||
* @return the HttpClientWrapper instance
|
||||
*/
|
||||
HttpClientWrapper getHttpClient();
|
||||
|
||||
/**
|
||||
* Returns whether response caching is enabled for this data source.
|
||||
*
|
||||
* @return true if caching is enabled, false otherwise
|
||||
*/
|
||||
boolean isResponseCachingEnabled();
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.datasources.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.http.HttpRequest;
|
||||
|
||||
/**
|
||||
* Wrapper around {@link java.net.http.HttpClient} to enable easier testing via
|
||||
* mocking.
|
||||
* <p>
|
||||
* This wrapper provides a simple interface for HTTP operations that can be
|
||||
* easily mocked in unit tests, avoiding the need to mock the final
|
||||
* {@link java.net.http.HttpClient} class directly.
|
||||
*/
|
||||
public interface HttpClientWrapper {
|
||||
|
||||
/**
|
||||
* Sends the given request using this client, blocking if necessary to get the
|
||||
* response.
|
||||
*
|
||||
* @param <T> the response body type
|
||||
* @param request the request
|
||||
* @param handler the response body handler
|
||||
* @return the response wrapper
|
||||
* @throws IOException if an I/O error occurs when sending or receiving
|
||||
* @throws InterruptedException if the operation is interrupted
|
||||
*/
|
||||
<T> HttpResponseWrapper<T> send(HttpRequest request, java.net.http.HttpResponse.BodyHandler<T> handler)
|
||||
throws IOException, InterruptedException;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
package ta4jexamples.datasources.http;
|
||||
|
||||
/**
|
||||
* Wrapper around {@link java.net.http.HttpResponse} to enable easier testing
|
||||
* via mocking.
|
||||
* <p>
|
||||
* This wrapper provides a simple interface for HTTP response operations that
|
||||
* can be easily mocked in unit tests, avoiding the need to mock the final
|
||||
* {@link java.net.http.HttpResponse} class directly.
|
||||
*
|
||||
* @param <T> the response body type
|
||||
*/
|
||||
public interface HttpResponseWrapper<T> {
|
||||
|
||||
/**
|
||||
* Returns the status code for this response.
|
||||
*
|
||||
* @return the status code
|
||||
*/
|
||||
int statusCode();
|
||||
|
||||
/**
|
||||
* Returns the body of this response.
|
||||
*
|
||||
* @return the response body
|
||||
*/
|
||||
T body();
|
||||
}
|
||||
Reference in New Issue
Block a user