diff --git a/AbstractusAPI/src/main/java/AbstractusAPI/exception/AbstractusException.java b/AbstractusAPI/src/main/java/AbstractusAPI/exception/AbstractusException.java index b3273b3..3da0bf7 100644 --- a/AbstractusAPI/src/main/java/AbstractusAPI/exception/AbstractusException.java +++ b/AbstractusAPI/src/main/java/AbstractusAPI/exception/AbstractusException.java @@ -10,6 +10,7 @@ public class AbstractusException extends RuntimeException { private final int errorCode; + public AbstractusException(String message, int errorCode) { this.message = message; this.errorCode = errorCode; diff --git a/AbstractusAPI/src/main/java/AbstractusAPI/http/query/Endpoint.java b/AbstractusAPI/src/main/java/AbstractusAPI/http/query/Endpoint.java new file mode 100644 index 0000000..d7d6f83 --- /dev/null +++ b/AbstractusAPI/src/main/java/AbstractusAPI/http/query/Endpoint.java @@ -0,0 +1,8 @@ +package AbstractusAPI.http.query; + +public record Endpoint(String... path) { + + public String getPath() { + return "/" + String.join("/", path); + } +} diff --git a/AbstractusAPI/src/main/java/AbstractusAPI/http/request/BasicRequestValidator.java b/AbstractusAPI/src/main/java/AbstractusAPI/http/request/BasicRequestValidator.java index 3f05dd1..8a04edd 100644 --- a/AbstractusAPI/src/main/java/AbstractusAPI/http/request/BasicRequestValidator.java +++ b/AbstractusAPI/src/main/java/AbstractusAPI/http/request/BasicRequestValidator.java @@ -1,77 +1,94 @@ package AbstractusAPI.http.request; +import AbstractusAPI.exception.AbstractusException; import AbstractusAPI.exception.client_error.*; import AbstractusAPI.exception.server_error.*; -import AbstractusAPI.exception.server_error.HTTPVersionNotSupportedException; import okhttp3.Response; import org.json.JSONObject; +import java.util.HashMap; +import java.util.Map; + /** * Basic implementation of {@link RequestValidator}. Handles most * status codes that can be returned from an API. */ public class BasicRequestValidator implements RequestValidator { + private static final Map> CLIENT_ERROR_MAP = new HashMap<>(); + private static final Map> SERVER_ERROR_MAP = new HashMap<>(); + + static { + // Client error mappings + CLIENT_ERROR_MAP.put(400, BadRequestException.class); + CLIENT_ERROR_MAP.put(401, UnauthorizedException.class); + CLIENT_ERROR_MAP.put(402, PaymentRequiredException.class); + CLIENT_ERROR_MAP.put(403, ForbiddenException.class); + CLIENT_ERROR_MAP.put(404, NotFoundException.class); + CLIENT_ERROR_MAP.put(405, MethodNotAllowedException.class); + CLIENT_ERROR_MAP.put(406, NotAcceptableException.class); + CLIENT_ERROR_MAP.put(407, ProxyAuthenticationRequiredException.class); + CLIENT_ERROR_MAP.put(408, RequestTimeoutException.class); + CLIENT_ERROR_MAP.put(409, ConflictException.class); + CLIENT_ERROR_MAP.put(410, GoneException.class); + CLIENT_ERROR_MAP.put(411, LengthRequiredException.class); + CLIENT_ERROR_MAP.put(412, PreconditionFailedException.class); + CLIENT_ERROR_MAP.put(413, PayloadTooLargeException.class); + CLIENT_ERROR_MAP.put(414, URITooLongException.class); + CLIENT_ERROR_MAP.put(415, UnsupportedMediaTypeException.class); + CLIENT_ERROR_MAP.put(416, RangeNotSatisfiableException.class); + CLIENT_ERROR_MAP.put(417, ExpectationFailedException.class); + CLIENT_ERROR_MAP.put(418, TeapotException.class); + CLIENT_ERROR_MAP.put(419, MisdirectedRequestException.class); + CLIENT_ERROR_MAP.put(422, UnprocessableEntityException.class); + CLIENT_ERROR_MAP.put(423, LockedException.class); + CLIENT_ERROR_MAP.put(424, FailedDependencyException.class); + CLIENT_ERROR_MAP.put(425, TooEarlyException.class); + CLIENT_ERROR_MAP.put(426, UpgradeRequiredException.class); + CLIENT_ERROR_MAP.put(428, PreconditionRequiredException.class); + CLIENT_ERROR_MAP.put(429, RateLimitingException.class); + CLIENT_ERROR_MAP.put(431, RequestHeaderFieldsTooLargeException.class); + CLIENT_ERROR_MAP.put(451, UnavailableForLegalReasonsException.class); + + // Server error mappings + SERVER_ERROR_MAP.put(500, InternalServerErrorException.class); + SERVER_ERROR_MAP.put(501, NotImplementedException.class); + SERVER_ERROR_MAP.put(502, BadGatewayException.class); + SERVER_ERROR_MAP.put(503, ServiceUnavailableException.class); + SERVER_ERROR_MAP.put(504, GatewayTimeoutException.class); + SERVER_ERROR_MAP.put(505, HTTPVersionNotSupportedException.class); + SERVER_ERROR_MAP.put(506, VariantAlsoNegotiatesException.class); + SERVER_ERROR_MAP.put(507, InsufficientStorageException.class); + SERVER_ERROR_MAP.put(508, LoopDetectedException.class); + SERVER_ERROR_MAP.put(510, NotExtendedException.class); + SERVER_ERROR_MAP.put(511, NetworkAuthenticationException.class); + } @Override public boolean validate(Response response, JSONObject returnObject) { if (response.isSuccessful()) { return true; } else { - String failString = returnObject.getString("cause"); - - switch (response.code()) { + String failString = returnObject.optString("cause", "Unknown error"); + int statusCode = response.code(); - // client errors - case 400 -> throw new BadRequestException(failString); - case 401 -> throw new UnauthorizedException(failString); - case 402 -> throw new PaymentRequiredException(failString); - case 403 -> throw new ForbiddenException(failString); - case 404 -> throw new NotFoundException(failString); - case 405 -> throw new MethodNotAllowedException(failString); - case 406 -> throw new NotAcceptableException(failString); - case 407 -> throw new ProxyAuthenticationRequiredException(failString); - case 408 -> throw new RequestTimeoutException(failString); - case 409 -> throw new ConflictException(failString); - case 410 -> throw new GoneException(failString); - case 411 -> throw new LengthRequiredException(failString); - case 412 -> throw new PreconditionFailedException(failString); - case 413 -> throw new PayloadTooLargeException(failString); - case 414 -> throw new URITooLongException(failString); - case 415 -> throw new UnsupportedMediaTypeException(failString); - case 416 -> throw new RangeNotSatisfiableException(failString); - case 417 -> throw new ExpectationFailedException(failString); - case 418 -> throw new TeapotException(failString); - case 419 -> throw new MisdirectedRequestException(failString); - case 422 -> throw new UnprocessableEntityException(failString); - case 423 -> throw new LockedException(failString); - case 424 -> throw new FailedDependencyException(failString); - case 425 -> throw new TooEarlyException(failString); - case 426 -> throw new UpgradeRequiredException(failString); - case 428 -> throw new PreconditionRequiredException(failString); - case 429 -> throw new RateLimitingException(failString); - case 431 -> throw new RequestHeaderFieldsTooLargeException(failString); - case 451 -> throw new UnavailableForLegalReasonsException(failString); - - // server errors - case 500 -> throw new InternalServerErrorException(failString); - case 501 -> throw new NotImplementedException(failString); - case 502 -> throw new BadGatewayException(failString); - case 503 -> throw new ServiceUnavailableException(failString); - case 504 -> throw new GatewayTimeoutException(failString); - case 505 -> throw new HTTPVersionNotSupportedException(failString); - case 506 -> throw new VariantAlsoNegotiatesException(failString); - case 507 -> throw new InsufficientStorageException(failString); - case 508 -> throw new LoopDetectedException(failString); - case 510 -> throw new NotExtendedException(failString); - case 511 -> throw new NetworkAuthenticationException(failString); - - default -> { - return false; - } + if (CLIENT_ERROR_MAP.containsKey(statusCode)) { + throwException(CLIENT_ERROR_MAP.get(statusCode), failString); + } else if (SERVER_ERROR_MAP.containsKey(statusCode)) { + throwException(SERVER_ERROR_MAP.get(statusCode), failString); + } else { + return false; + } + } + return false; + } + private void throwException (Class exceptionClass, String message){ + try { + throw exceptionClass.getConstructor(String.class).newInstance(message); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to instantiate exception for status code", e); } } } -} diff --git a/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestController.java b/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestController.java index 7df2979..97aa0d1 100644 --- a/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestController.java +++ b/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestController.java @@ -1,5 +1,6 @@ package AbstractusAPI.http.request; +import AbstractusAPI.http.query.Endpoint; import AbstractusAPI.http.query.Query; import AbstractusAPI.http.query.QueryParameter; import okhttp3.OkHttpClient; @@ -22,6 +23,7 @@ public class RequestController { private final String hostname; private final RequestFactory requestFactory; + public RequestController(String origin, String hostname) { this.origin = origin; this.hostname = hostname; @@ -46,24 +48,57 @@ public RequestController(String origin, String hostname, OkHttpClient client, Re this.requestFactory = new RequestFactory(client, validator); } + public void setAutoClearCache(boolean autoClear) { + requestFactory.setAutoClearCache(autoClear); + } + + public CompletableFuture sendRequestAsync() { + try { + URL url = new URL(origin, hostname, "/"); + Query query = new Query(url); + query.addParameter(queryParameters.toArray(new QueryParameter[0])); + return requestFactory.sendAsync(query); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } - public CompletableFuture sendRequestAsync(String endpoint, QueryParameter... params) { + public CompletableFuture sendRequestAsync(QueryParameter... params) { try { - URL url = new URL(origin, hostname, "/" + endpoint); + URL url = new URL(origin, hostname, "/"); Query query = new Query(url); query.addParameter(params); - query.addParameter(queryParameters.toArray(new QueryParameter[queryParameters.size()])); + query.addParameter(queryParameters.toArray(new QueryParameter[0])); + return requestFactory.sendAsync(query); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + + public CompletableFuture sendRequestAsync(Endpoint endpoint, QueryParameter... params) { + try { + + // Create the URL object + URL url = new URL(origin, hostname, endpoint.getPath()); + + // Create and configure the Query object + Query query = new Query(url); + query.addParameter(params); + query.addParameter(queryParameters.toArray(new QueryParameter[0])); + + // Send the request asynchronously return requestFactory.sendAsync(query); } catch (MalformedURLException e) { throw new RuntimeException(e); } } - public CompletableFuture sendRequestAsync(String endpoint) { + public CompletableFuture sendRequestAsync(Endpoint endpoint) { try { - URL url = new URL(origin, hostname, "/" + endpoint); + URL url = new URL(origin, hostname, endpoint.getPath()); Query query = new Query(url); - query.addParameter(queryParameters.toArray(new QueryParameter[queryParameters.size()])); + query.addParameter(queryParameters.toArray(new QueryParameter[0])); return requestFactory.sendAsync(query); } catch (MalformedURLException e) { throw new RuntimeException(e); @@ -75,18 +110,18 @@ public void clearCache() { } public void setApiKey(UUID apiKey) { - addPermanentQueryParameter(new QueryParameter("key", apiKey.toString())); + addQueryParameter(new QueryParameter("key", apiKey.toString())); } /** * Adds a parameter to every request being made. * @param parameter The parameter to add. */ - public void addPermanentQueryParameter(QueryParameter parameter) { + public void addQueryParameter(QueryParameter parameter) { queryParameters.add(parameter); } - public void removePermanentQueryParameter(QueryParameter parameter) { + public void removeQueryParameter(QueryParameter parameter) { queryParameters.remove(parameter); } } diff --git a/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestFactory.java b/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestFactory.java index 4351c5d..6f3fabc 100644 --- a/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestFactory.java +++ b/AbstractusAPI/src/main/java/AbstractusAPI/http/request/RequestFactory.java @@ -20,6 +20,7 @@ public class RequestFactory { private final OkHttpClient client; private final RequestValidator validator; + private boolean autoClearCache; protected RequestFactory() { this.validator = new BasicRequestValidator(); @@ -41,6 +42,10 @@ protected RequestFactory(OkHttpClient client, RequestValidator validator) { this.validator = validator; } + protected void setAutoClearCache(boolean autoClear) { + this.autoClearCache = autoClear; + } + private OkHttpClient getClient() { File httpCacheDirectory = new File("./cacheDir", "http-cache"); int cacheSize = 10 * 1024 * 1024; // 10 MiB @@ -69,6 +74,8 @@ public void clearCache() { public CompletableFuture sendAsync(Query query) { Request request = new Request.Builder().url(query.createRequest()).build(); + if(autoClearCache) clearCache(); + return CompletableFuture.supplyAsync(() -> { try { Response response = client.newCall(request).execute();