import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class ApiPayloadWrapper {
private static final ConcurrentHashMap<String, RateLimiter> rateLimiters = new ConcurrentHashMap<>();
public static <T> T wrapPayload(String apiEndpoint, Runnable payloadHandler, int maxRequestsPerSecond) {
// Get or create a rate limiter for the endpoint.
RateLimiter limiter = rateLimiters.computeIfAbsent(apiEndpoint, k -> new RateLimiter(maxRequestsPerSecond));
// Acquire a token from the rate limiter. Blocks until a token is available.
try {
limiter.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupted status
throw new RuntimeException("Rate limit interrupted", e);
}
// Execute the payload handler.
try {
payloadHandler.run();
} catch (Exception e) {
//Wrap the exception with a maintenance error.
throw new MaintenanceError("API Payload Error: " + e.getMessage(), e);
}
return null; // Return null or the result of the payload handler, if applicable.
}
public static class RateLimiter {
private final int maxRequestsPerSecond;
private final AtomicInteger requestCount = new AtomicInteger(0);
private long lastResetTime = System.currentTimeMillis();
public RateLimiter(int maxRequestsPerSecond) {
this.maxRequestsPerSecond = maxRequestsPerSecond;
}
public synchronized boolean acquire() throws InterruptedException {
long currentTime = System.currentTimeMillis();
if (currentTime - lastResetTime > 1000) {
// Reset the counter if a second has passed.
requestCount.set(0);
lastResetTime = currentTime;
}
if (requestCount.get() < maxRequestsPerSecond) {
requestCount.incrementAndGet();
return true;
} else {
// Wait until a token is available.
long sleepTime = 1000 - (currentTime - lastResetTime) % 1000; //Ensure we don't wait longer than 1s
Thread.sleep(sleepTime);
return acquire(); //Recursive call to try again
}
}
}
public static class MaintenanceError extends RuntimeException {
private final Throwable cause;
public MaintenanceError(String message, Throwable cause) {
super(message);
this.cause = cause;
}
public Throwable getCause() {
return cause;
}
}
public static void main(String[] args) {
// Example Usage
try {
// Simulate an API endpoint.
Runnable apiCall = () -> {
System.out.println("Making API call...");
//Simulate a possible error
if(Math.random() < 0.2){
throw new RuntimeException("Simulated API error");
}
System.out.println("API call completed.");
};
// Wrap the API call with a rate limiter.
T result = wrapPayload("exampleEndpoint", apiCall, 5); // 5 requests per second
if (result != null) {
System.out.println("API call result: " + result);
}
} catch (MaintenanceError e) {
System.err.println("Maintenance Error: " + e.getMessage());
System.err.println("Caused by: " + e.getCause().getMessage());
}
}
}
Add your comment