diff --git a/README.md b/README.md index 906b357..a7892e5 100644 --- a/README.md +++ b/README.md @@ -24,27 +24,23 @@

Build Status Last Commit - Maven Central + Maven Central



### easycodef-java-v2 -- **[EasyCodef V2 Wiki](https://github.com/codef-io/easycodef-java-v2/wiki)** - - Latest version support feature - 본 라이브러리는 현재 알파 버전 개발중인 버전으로, 2025년 상반기 정식 릴리즈 예정입니다. - - → EasyCodef.requestProduct(EasyCodefRequest request) : 비인증 상품 호출 - → EasyCodef.requestSimpleAuthCertification() : 사용자 본인 인증(간편 인증) 완료 호출 +> [!TIP] +> - **[EasyCodef V2 Wiki](https://github.com/codef-io/easycodef-java-v2/wiki)**
-- [Codef Homepage](https://codef.io/) -- [Codef API Developer Guide](https://developer.codef.io/) -- [Hectodata Homepage](https://hectodata.co.kr/) -- [Hecto Tech Blog](https://blog.hectodata.co.kr/) +> [!NOTE] +> - [Codef Homepage](https://codef.io/) +> - [Codef API Developer Guide](https://developer.codef.io/) +> - [Hectodata Homepage](https://hectodata.co.kr/) +> - [Hecto Tech Blog](https://blog.hectodata.co.kr/)

diff --git a/build.gradle.kts b/build.gradle.kts index 6734d91..8c0992a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ plugins { group = "io.codef.api" -version = "2.0.0-alpha-004" +version = "2.0.0-alpha-005" signing { useInMemoryPgpKeys( diff --git a/src/main/java/io/codef/api/CodefValidator.java b/src/main/java/io/codef/api/CodefValidator.java index 30ab945..5da87b8 100644 --- a/src/main/java/io/codef/api/CodefValidator.java +++ b/src/main/java/io/codef/api/CodefValidator.java @@ -10,12 +10,18 @@ public class CodefValidator { private CodefValidator() { } - public static T requireNonNullElseThrow(T object, CodefError codefError) { + public static T requireNonNullElseThrow( + T object, + CodefError codefError + ) { return Optional.ofNullable(object) .orElseThrow(() -> CodefException.from(codefError)); } - public static void requireValidUUIDPattern(String uuid, CodefError codefError) { + public static void requireValidUUIDPattern( + String uuid, + CodefError codefError + ) { final String UUID_REGULAR_EXPRESSION = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"; Optional.ofNullable(uuid) diff --git a/src/main/java/io/codef/api/EasyCodef.java b/src/main/java/io/codef/api/EasyCodef.java index 4a6c684..01d741a 100644 --- a/src/main/java/io/codef/api/EasyCodef.java +++ b/src/main/java/io/codef/api/EasyCodef.java @@ -5,19 +5,26 @@ import io.codef.api.dto.CodefSimpleAuth; import io.codef.api.dto.EasyCodefRequest; import io.codef.api.dto.EasyCodefResponse; -import io.codef.api.error.CodefError; import io.codef.api.error.CodefException; +import io.codef.api.storage.MultipleRequestStorage; +import io.codef.api.storage.SimpleAuthStorage; import io.codef.api.util.RsaUtil; import java.security.PublicKey; -import java.util.HashMap; -import java.util.Optional; - +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.*; +import java.util.stream.IntStream; + +import static io.codef.api.dto.EasyCodefRequest.SSO_ID; import static io.codef.api.dto.EasyCodefRequest.TRUE; public class EasyCodef { - private final HashMap simpleAuthRequestStorage = new HashMap<>(); - + private static final long REQUEST_DELAY_MS = 700L; + private final SimpleAuthStorage simpleAuthStorage; + private final MultipleRequestStorage multipleRequestStorage; private final PublicKey publicKey; private final CodefClientType clientType; private final EasyCodefToken easyCodefToken; @@ -26,60 +33,207 @@ protected EasyCodef(EasyCodefBuilder builder, EasyCodefToken easyCodefToken) { this.publicKey = RsaUtil.generatePublicKey(builder.getPublicKey()); this.clientType = builder.getClientType(); this.easyCodefToken = easyCodefToken; + this.simpleAuthStorage = new SimpleAuthStorage(); + this.multipleRequestStorage = new MultipleRequestStorage(); } + /** + * 단일 상품 요청 + */ public EasyCodefResponse requestProduct(EasyCodefRequest request) throws CodefException { - final String requestUrl = clientType.getHost() + request.path(); - final EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); + String requestUrl = buildRequestUrl(request); + EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); - final EasyCodefResponse easyCodefResponse = EasyCodefConnector.requestProduct(request, validToken, requestUrl); + EasyCodefResponse response = EasyCodefConnector.requestProduct(request, validToken, requestUrl); + simpleAuthStorage.storeIfRequired(request, response, requestUrl); - storeIfSimpleAuthResponseRequired(request, easyCodefResponse, requestUrl); - return easyCodefResponse; + return response; } + /** + * 다중 상품 요청 + */ + public EasyCodefResponse requestMultipleProduct(List requests) throws CodefException { + validateRequests(requests); + + String uuid = UUID.randomUUID().toString(); + assignSsoId(requests, uuid); + + var executors = createExecutors(); + try { + return processMultipleRequests(requests, executors); + } finally { + cleanupExecutors(executors); + } + } + + /** + * 단건 간편인증 완료 요청 + */ public EasyCodefResponse requestSimpleAuthCertification(String transactionId) throws CodefException { - final CodefSimpleAuth codefSimpleAuth = simpleAuthRequestStorage.get(transactionId); - CodefValidator.requireNonNullElseThrow(codefSimpleAuth, CodefError.SIMPLE_AUTH_FAILED); + CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId); + + EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth); + EasyCodefResponse response = executeSimpleAuthRequest(enrichedRequest, simpleAuth.requestUrl()); + + simpleAuthStorage.updateIfRequired( + simpleAuth.requestUrl(), + enrichedRequest, + response, + transactionId + ); + + return response; + } + + /** + * 다건 간편인증 완료 요청 + */ + public List requestMultipleSimpleAuthCertification(String transactionId) throws CodefException { + CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId); + + EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth); + EasyCodefResponse firstResponse = executeSimpleAuthRequest(enrichedRequest, simpleAuth.requestUrl()); + + simpleAuthStorage.updateIfRequired( + simpleAuth.requestUrl(), + enrichedRequest, + firstResponse, + transactionId + ); + + return isSuccessful(firstResponse) + ? combineWithRemainingResponses(firstResponse, transactionId) + : List.of(firstResponse); + } + + + // Private helper methods + + private String buildRequestUrl(EasyCodefRequest request) { + return clientType.getHost() + request.path(); + } + + private EasyCodefRequest enrichRequestWithTwoWayInfo(CodefSimpleAuth simpleAuth) { + EasyCodefRequest request = simpleAuth.request(); + Map body = request.requestBody(); + + body.put(EasyCodefRequest.IS_TWO_WAY, true); + body.put(EasyCodefRequest.SIMPLE_AUTH, TRUE); + body.put(EasyCodefRequest.TWO_WAY_INFO, simpleAuth.response().data()); + + return request; + } + + private EasyCodefResponse executeSimpleAuthRequest(EasyCodefRequest request, String requestUrl) + throws CodefException { + EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); + return EasyCodefConnector.requestProduct(request, validToken, requestUrl); + } - final String requestUrl = codefSimpleAuth.requestUrl(); - final EasyCodefRequest request = codefSimpleAuth.request(); + private boolean isSuccessful(EasyCodefResponse response) { + return CodefResponseCode.CF_00000.equals(response.code()); + } + + private List combineWithRemainingResponses( + EasyCodefResponse firstResponse, + String transactionId + ) throws CodefException { + List remainingResponses = multipleRequestStorage.getRemainingResponses(transactionId); + List allResponses = new ArrayList<>(remainingResponses); + allResponses.add(firstResponse); + return allResponses; + } + + private void validateRequests(List requests) { + if (requests == null || requests.isEmpty()) { + throw new IllegalArgumentException("Requests cannot be null or empty"); + } + } + + private void assignSsoId(List requests, String uuid) { + requests.forEach(request -> request.requestBody().put(SSO_ID, uuid)); + } + + private CodefExecutors createExecutors() { + return new CodefExecutors( + Executors.newScheduledThreadPool(1), + Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory()) + ); + } + + private EasyCodefResponse processMultipleRequests( + List requests, + CodefExecutors codefExecutors + ) throws CodefException { + List> futures = scheduleRequests(requests, codefExecutors); - addTwoWayInfo(request, codefSimpleAuth); + CompletableFuture firstCompleted = CompletableFuture.anyOf( + futures.toArray(new CompletableFuture[0]) + ).thenApply(result -> (EasyCodefResponse) result); - final EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); - final EasyCodefResponse easyCodefResponse = EasyCodefConnector.requestProduct(request, validToken, requestUrl); + EasyCodefResponse result = firstCompleted.join(); + multipleRequestStorage.store(result.transactionId(), futures); - updateSimpleAuthResponseRequired(requestUrl, request, easyCodefResponse, transactionId); - return easyCodefResponse; + return result; } - private void addTwoWayInfo( + private List> scheduleRequests( + List requests, + CodefExecutors codefExecutors + ) { + return IntStream.range(0, requests.size()) + .mapToObj(i -> scheduleRequest(requests.get(i), i * REQUEST_DELAY_MS, codefExecutors)) + .toList(); + } + + private CompletableFuture scheduleRequest( EasyCodefRequest request, - CodefSimpleAuth codefSimpleAuth + long delayMs, + CodefExecutors codefExecutors ) { - request.requestBody().put(EasyCodefRequest.IS_TWO_WAY, true); - request.requestBody().put(EasyCodefRequest.SIMPLE_AUTH, TRUE); - request.requestBody().put(EasyCodefRequest.TWO_WAY_INFO, codefSimpleAuth.response().data()); + CompletableFuture future = new CompletableFuture<>(); + + codefExecutors.scheduler.schedule( + () -> executeRequest(request, codefExecutors.virtualThreadExecutor, future), + delayMs, + TimeUnit.MILLISECONDS + ); + + return future; } - private void storeIfSimpleAuthResponseRequired(EasyCodefRequest request, EasyCodefResponse easyCodefResponse, String requestUrl) { - Optional.ofNullable(easyCodefResponse.code()).filter(code -> code.equals(CodefResponseCode.CF_03002)).ifPresent(code -> { - CodefSimpleAuth codefSimpleAuth = new CodefSimpleAuth(requestUrl, request, easyCodefResponse); - simpleAuthRequestStorage.put(easyCodefResponse.transactionId(), codefSimpleAuth); + private void executeRequest( + EasyCodefRequest request, + Executor executor, + CompletableFuture future + ) { + CompletableFuture.supplyAsync(() -> { + try { + return requestProduct(request); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executor).whenComplete((response, ex) -> { + if (ex != null) { + future.completeExceptionally(ex); + } else { + future.complete(response); + } }); } - private void updateSimpleAuthResponseRequired(String path, EasyCodefRequest request, EasyCodefResponse easyCodefResponse, String transactionId) { - Optional.ofNullable(easyCodefResponse.code()) - .filter(code -> code.equals(CodefResponseCode.CF_03002)) - .ifPresentOrElse(code -> { - CodefSimpleAuth newCodefSimpleAuth = new CodefSimpleAuth(path, request, easyCodefResponse); - simpleAuthRequestStorage.put(transactionId, newCodefSimpleAuth); - }, () -> simpleAuthRequestStorage.remove(transactionId)); + private void cleanupExecutors(CodefExecutors codefExecutors) { + codefExecutors.scheduler.shutdown(); } public PublicKey getPublicKey() { return publicKey; } + + private record CodefExecutors( + ScheduledExecutorService scheduler, + Executor virtualThreadExecutor + ) { + } } \ No newline at end of file diff --git a/src/main/java/io/codef/api/EasyCodefBuilder.java b/src/main/java/io/codef/api/EasyCodefBuilder.java index f173e6f..504c3ba 100644 --- a/src/main/java/io/codef/api/EasyCodefBuilder.java +++ b/src/main/java/io/codef/api/EasyCodefBuilder.java @@ -17,7 +17,7 @@ public static EasyCodefBuilder builder() { } public EasyCodefBuilder publicKey(String publicKey) { - this.publicKey = CodefValidator.requireNonNullElseThrow(publicKey, CodefError.INVALID_PUBLIC_KEY); + this.publicKey = CodefValidator.requireNonNullElseThrow(publicKey, CodefError.NULL_PUBLIC_KEY); return this; } diff --git a/src/main/java/io/codef/api/EasyCodefConnector.java b/src/main/java/io/codef/api/EasyCodefConnector.java index abfc0d2..eb5346b 100644 --- a/src/main/java/io/codef/api/EasyCodefConnector.java +++ b/src/main/java/io/codef/api/EasyCodefConnector.java @@ -9,20 +9,25 @@ import io.codef.api.error.CodefException; import io.codef.api.util.HttpClientUtil; import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.io.entity.StringEntity; +import java.io.IOException; import java.nio.charset.StandardCharsets; import static io.codef.api.dto.EasyCodefRequest.BASIC_TOKEN_FORMAT; import static io.codef.api.dto.EasyCodefRequest.BEARER_TOKEN_FORMAT; import static org.apache.hc.core5.http.HttpHeaders.AUTHORIZATION; -public final class EasyCodefConnector { +public class EasyCodefConnector { private static final ResponseHandler responseHandler = new ResponseHandler(); private EasyCodefConnector() { } + /** + * 상품 요청 + */ public static EasyCodefResponse requestProduct( EasyCodefRequest request, EasyCodefToken token, @@ -32,12 +37,9 @@ public static EasyCodefResponse requestProduct( return executeRequest(httpRequest, responseHandler::handleProductResponse); } - private static HttpPost createTokenRequest(String codefOAuthToken) { - HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN); - httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken)); - return httpPost; - } - + /** + * 액세스 토큰 요청 + */ public static String requestToken( String codefOAuthToken ) throws CodefException { @@ -45,6 +47,13 @@ public static String requestToken( return executeRequest(request, responseHandler::handleTokenResponse); } + + private static HttpPost createTokenRequest(String codefOAuthToken) { + HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN); + httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken)); + return httpPost; + } + private static HttpPost createProductRequest( EasyCodefRequest request, EasyCodefToken token, @@ -65,10 +74,13 @@ private static T executeRequest( ) { try (var httpClient = HttpClientUtil.createClient()) { return httpClient.execute(request, processor::process); - } catch (CodefException e) { - throw e; - } catch (Exception e) { - throw CodefException.of(CodefError.INTERNAL_SERVER_ERROR, e); + } catch (IOException exception) { + throw CodefException.of(CodefError.IO_ERROR, exception); } } + + @FunctionalInterface + private interface ResponseProcessor { + T process(ClassicHttpResponse response) throws CodefException; + } } \ No newline at end of file diff --git a/src/main/java/io/codef/api/EasyCodefToken.java b/src/main/java/io/codef/api/EasyCodefToken.java index 26de38d..6f60f4a 100644 --- a/src/main/java/io/codef/api/EasyCodefToken.java +++ b/src/main/java/io/codef/api/EasyCodefToken.java @@ -2,30 +2,41 @@ import java.time.LocalDateTime; import java.util.Base64; +import java.util.Optional; public class EasyCodefToken { - private final String oauthToken; - private String accessToken; - private LocalDateTime expiresAt; + private final String oauthToken; + private String accessToken; + private LocalDateTime expiresAt; protected EasyCodefToken(EasyCodefBuilder builder) { final int VALIDITY_PERIOD_DAYS = 7; final String DELIMITER = ":"; String combinedKey = String.join(DELIMITER, builder.getClientId().toString(), builder.getClientSecret().toString()); + this.oauthToken = Base64.getEncoder().encodeToString(combinedKey.getBytes()); this.accessToken = EasyCodefConnector.requestToken(oauthToken); this.expiresAt = LocalDateTime.now().plusDays(VALIDITY_PERIOD_DAYS); } public EasyCodefToken validateAndRefreshToken() { - if (expiresAt.isBefore(LocalDateTime.now().plusHours(24))) { - this.accessToken = EasyCodefConnector.requestToken(oauthToken); - this.expiresAt = LocalDateTime.now().plusDays(7); - } + Optional.of(expiresAt) + .filter(this::isTokenExpiringSoon) + .ifPresent(expiry -> refreshToken()); + return this; } + private boolean isTokenExpiringSoon(LocalDateTime expiry) { + return expiry.isBefore(LocalDateTime.now().plusHours(24)); + } + + private void refreshToken() { + this.accessToken = EasyCodefConnector.requestToken(oauthToken); + this.expiresAt = LocalDateTime.now().plusDays(7); + } + public String getAccessToken() { return accessToken; } diff --git a/src/main/java/io/codef/api/ResponseHandler.java b/src/main/java/io/codef/api/ResponseHandler.java index 0e5b5e0..532365a 100644 --- a/src/main/java/io/codef/api/ResponseHandler.java +++ b/src/main/java/io/codef/api/ResponseHandler.java @@ -13,66 +13,131 @@ import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.Optional; import static io.codef.api.dto.EasyCodefRequest.ACCESS_TOKEN; import static io.codef.api.dto.EasyCodefResponse.DATA; import static io.codef.api.dto.EasyCodefResponse.RESULT; public class ResponseHandler { + private static final String UTF_8 = StandardCharsets.UTF_8.toString(); + public ResponseHandler() { } - public String handleTokenResponse(ClassicHttpResponse response) { + /** + * 토큰 응답 처리 + */ + public String handleTokenResponse(ClassicHttpResponse response) throws CodefException { + HttpStatusHandler handler = new HttpStatusHandler<>( + response.getCode(), + CodefError.OAUTH_UNAUTHORIZED, + CodefError.OAUTH_INTERNAL_ERROR, + this::parseAccessToken + ); + + return handleHttpResponse(response, handler, false); + } + + /** + * 상품 응답 처리 + */ + public EasyCodefResponse handleProductResponse(ClassicHttpResponse response) throws CodefException { + HttpStatusHandler handler = new HttpStatusHandler<>( + response.getCode(), + CodefError.CODEF_API_UNAUTHORIZED, + CodefError.CODEF_API_SERVER_ERROR, + this::parseProductResponse + ); + + return handleHttpResponse(response, handler, true); + } + + /** + * 공통 HTTP 응답 처리 로직 + */ + private T handleHttpResponse( + ClassicHttpResponse response, + HttpStatusHandler handler, + boolean requireUrlDecoding + ) throws CodefException { + String responseBody = extractResponseBody(response, requireUrlDecoding); + return handleStatusCode(responseBody, handler); + } + + /** + * HTTP 응답 본문 추출 + */ + private String extractResponseBody( + ClassicHttpResponse response, + boolean requiresDecoding + ) { try { - final String responseBody = EntityUtils.toString(response.getEntity()); - final int httpStatusCode = response.getCode(); - - return switch (httpStatusCode) { - case HttpStatus.SC_OK -> parseAccessToken(responseBody); - case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, responseBody); - default -> throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, responseBody); - }; - } catch (IOException exception) { - throw CodefException.of(CodefError.IO_ERROR, exception); - } catch (ParseException exception) { - throw CodefException.of(CodefError.PARSE_ERROR, exception); + String responseBody = EntityUtils.toString(response.getEntity()); + return requiresDecoding ? URLDecoder.decode(responseBody, UTF_8) : responseBody; + } catch (IOException ioException) { + throw CodefException.of(CodefError.IO_ERROR, ioException); + } catch (ParseException parseException) { + throw CodefException.of(CodefError.PARSE_ERROR, parseException); } } - public EasyCodefResponse handleProductResponse( - ClassicHttpResponse response - ) throws CodefException { + /** + * 액세스 토큰 파싱 + */ + private String parseAccessToken(String responseBody) throws CodefException { try { - final String httpResponse = EntityUtils.toString(response.getEntity()); - final int httpStatusCode = response.getCode(); - - return switch (httpStatusCode) { - case HttpStatus.SC_OK -> { - final var decodedResponse = URLDecoder.decode(httpResponse, StandardCharsets.UTF_8); - yield parseProductResponse(decodedResponse); - } - case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, httpResponse); - default -> throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, httpResponse); - }; - } catch (IOException exception) { - throw CodefException.of(CodefError.IO_ERROR, exception); - } catch (ParseException exception) { + return JSON.parseObject(responseBody).getString(ACCESS_TOKEN); + } catch (Exception exception) { throw CodefException.of(CodefError.PARSE_ERROR, exception); } } + /** + * 상품 응답 파싱 + */ + private EasyCodefResponse parseProductResponse(String responseBody) throws CodefException { + JSONObject jsonResponse = JSON.parseObject(responseBody); + + EasyCodefResponse.Result result = Optional.ofNullable(jsonResponse.getJSONObject(RESULT)) + .map(object -> object.to(EasyCodefResponse.Result.class)) + .orElseThrow(() -> CodefException.from(CodefError.PARSE_ERROR)); - private String parseAccessToken(String responseBody) { - return JSON.parseObject(responseBody).getString(ACCESS_TOKEN); + Object data = Optional.ofNullable(jsonResponse.getJSONObject(DATA)) + .map(obj -> obj.to(Object.class)) + .orElseThrow(() -> CodefException.from(CodefError.PARSE_ERROR)); + + return new EasyCodefResponse(result, data); + } + + /** + * HTTP 상태 코드에 따른 처리 + */ + private T handleStatusCode(String responseBody, HttpStatusHandler handler) throws CodefException { + return switch (handler.statusCode) { + case HttpStatus.SC_OK -> handler.successHandler.parse(responseBody); + case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(handler.unauthorizedError, responseBody); + default -> throw CodefException.of(handler.defaultError, responseBody); + }; } - private EasyCodefResponse parseProductResponse(String decodedResponse) { - JSONObject jsonResponseObject = JSON.parseObject(decodedResponse); + /** + * HTTP 응답 처리를 위한 공통 인터페이스 + */ + private interface ResponseParser { - EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject(RESULT).to(EasyCodefResponse.Result.class); + T parse(String responseBody) throws CodefException; - Object dataResponse = jsonResponseObject.getJSONObject(DATA).to(Object.class); + } - return new EasyCodefResponse(resultResponse, dataResponse); + /** + * HTTP 응답 상태 코드에 따른 처리를 위한 레코드 + */ + private record HttpStatusHandler( + int statusCode, + CodefError unauthorizedError, + CodefError defaultError, + ResponseParser successHandler + ) { } } \ No newline at end of file diff --git a/src/main/java/io/codef/api/ResponseProcessor.java b/src/main/java/io/codef/api/ResponseProcessor.java deleted file mode 100644 index f30a00e..0000000 --- a/src/main/java/io/codef/api/ResponseProcessor.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.codef.api; - -import io.codef.api.error.CodefException; -import org.apache.hc.core5.http.ClassicHttpResponse; - -@FunctionalInterface -public interface ResponseProcessor { - T process(ClassicHttpResponse response) throws CodefException; -} \ No newline at end of file diff --git a/src/main/java/io/codef/api/constants/CodefReferenceUrl.java b/src/main/java/io/codef/api/constants/CodefReferenceUrl.java index 64f764c..a0c8b75 100644 --- a/src/main/java/io/codef/api/constants/CodefReferenceUrl.java +++ b/src/main/java/io/codef/api/constants/CodefReferenceUrl.java @@ -5,16 +5,16 @@ public enum CodefReferenceUrl { DEV_GUIDE_REST_API("https://developer.codef.io/common-guide/rest-api"), GITHUB("https://github.com/codef-io/easycodef-java-v2"), PRODUCT("https://developer.codef.io/product/api"), - TECH_INQUIRY("https://codef.io/cs/inquiry"); + TECH_INQUIRY("https://codef.io/cs/inquiry"), + MULTIPLE_REQUEST("https://developer.codef.io/common-guide/multiple-requests"); + private static final String MESSAGE_FORMAT = "→ For detailed information, please visit '%s'"; private final String url; CodefReferenceUrl(String url) { this.url = url; } - private static final String MESSAGE_FORMAT = "→ For detailed information, please visit '%s'"; - public String getUrl() { return String.format(MESSAGE_FORMAT, url); } diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequest.java b/src/main/java/io/codef/api/dto/EasyCodefRequest.java index 32cba68..dac0fff 100644 --- a/src/main/java/io/codef/api/dto/EasyCodefRequest.java +++ b/src/main/java/io/codef/api/dto/EasyCodefRequest.java @@ -23,6 +23,7 @@ public record EasyCodefRequest( /** * Request Body Key Constant */ + public static final String SSO_ID = "id"; public static final String IS_TWO_WAY = "is2Way"; public static final String SIMPLE_AUTH = "simpleAuth"; public static final String TWO_WAY_INFO = "twoWayInfo"; diff --git a/src/main/java/io/codef/api/error/CodefError.java b/src/main/java/io/codef/api/error/CodefError.java index 8a26b63..8b70252 100644 --- a/src/main/java/io/codef/api/error/CodefError.java +++ b/src/main/java/io/codef/api/error/CodefError.java @@ -11,10 +11,6 @@ public enum CodefError { "clientSecret must be a properly formatted UUID string. Please check your clientSecret and ensure it matches the UUID format.", CodefReferenceUrl.KEY ), - INVALID_PUBLIC_KEY( - "publicKey is required and cannot be null.", - CodefReferenceUrl.KEY - ), NULL_CLIENT_ID( "clientId is required and cannot be null.", CodefReferenceUrl.KEY @@ -39,6 +35,10 @@ public enum CodefError { "Failed to authenticate with the Codef OAuth server (401 Unauthorized). Please verify your clientId and clientSecret values.", CodefReferenceUrl.KEY ), + CODEF_API_UNAUTHORIZED( + "Failed to authenticate with the Codef API server (401 Unauthorized). Please verify your clientId and clientSecret values.", + CodefReferenceUrl.KEY + ), OAUTH_INTERNAL_ERROR( "An error occurred on the Codef OAuth server. Please try again later, or contact support if the issue persists.", CodefReferenceUrl.KEY @@ -86,6 +86,10 @@ public enum CodefError { SIMPLE_AUTH_FAILED( "No initial request data is saved for the specified transaction ID.", CodefReferenceUrl.TECH_INQUIRY + ), + NO_RESPONSE_RECEIVED( + "No responses were received on multiple request", + CodefReferenceUrl.MULTIPLE_REQUEST ); diff --git a/src/main/java/io/codef/api/error/CodefException.java b/src/main/java/io/codef/api/error/CodefException.java index c708e33..98537a7 100644 --- a/src/main/java/io/codef/api/error/CodefException.java +++ b/src/main/java/io/codef/api/error/CodefException.java @@ -43,4 +43,8 @@ public static CodefException of( ) { return new CodefException(codefError, extraMessage); } + + public CodefError getError() { + return codefError; + } } diff --git a/src/main/java/io/codef/api/storage/MultipleRequestStorage.java b/src/main/java/io/codef/api/storage/MultipleRequestStorage.java new file mode 100644 index 0000000..12c97c1 --- /dev/null +++ b/src/main/java/io/codef/api/storage/MultipleRequestStorage.java @@ -0,0 +1,51 @@ +package io.codef.api.storage; + +import io.codef.api.CodefValidator; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +public class MultipleRequestStorage { + private final Map>> storage = new HashMap<>(); + + public List getRemainingResponses(String transactionId) throws CodefException { + final List> futures = storage.get(transactionId); + CodefValidator.requireNonNullElseThrow(futures, CodefError.SIMPLE_AUTH_FAILED); + + try { + CompletableFuture allDone = CompletableFuture.allOf( + futures.toArray(new CompletableFuture[0]) + ); + allDone.join(); + + List results = futures.stream() + .map(this::safeJoin) + .filter(Objects::nonNull) + .filter(response -> !Objects.equals(response.transactionId(), transactionId)) + .toList(); + + storage.remove(transactionId); + return results; + } catch (Exception e) { + throw CodefException.from(CodefError.SIMPLE_AUTH_FAILED); + } + } + + private EasyCodefResponse safeJoin(CompletableFuture future) { + try { + return future.join(); + } catch (Exception e) { + throw CodefException.of(CodefError.SIMPLE_AUTH_FAILED, e); + } + } + + public void store(String transactionId, List> futures) { + storage.put(transactionId, futures); + } +} \ No newline at end of file diff --git a/src/main/java/io/codef/api/storage/SimpleAuthStorage.java b/src/main/java/io/codef/api/storage/SimpleAuthStorage.java new file mode 100644 index 0000000..6f6b69c --- /dev/null +++ b/src/main/java/io/codef/api/storage/SimpleAuthStorage.java @@ -0,0 +1,43 @@ +package io.codef.api.storage; + +import io.codef.api.constants.CodefResponseCode; +import io.codef.api.dto.CodefSimpleAuth; +import io.codef.api.dto.EasyCodefRequest; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class SimpleAuthStorage { + private final Map storage = new HashMap<>(); + + public void storeIfRequired(EasyCodefRequest request, EasyCodefResponse response, String requestUrl) { + Optional.ofNullable(response.code()) + .filter(code -> code.equals(CodefResponseCode.CF_03002)) + .ifPresent(code -> { + CodefSimpleAuth simpleAuth = new CodefSimpleAuth(requestUrl, request, response); + storage.put(response.transactionId(), simpleAuth); + }); + } + + public CodefSimpleAuth get(String transactionId) throws CodefException { + return Optional.ofNullable(storage.get(transactionId)) + .orElseThrow(() -> CodefException.from(CodefError.SIMPLE_AUTH_FAILED)); + } + + public void updateIfRequired(String path, EasyCodefRequest request, + EasyCodefResponse response, String transactionId) { + Optional.ofNullable(response.code()) + .filter(code -> code.equals(CodefResponseCode.CF_03002)) + .ifPresentOrElse( + code -> { + CodefSimpleAuth newAuth = new CodefSimpleAuth(path, request, response); + storage.put(transactionId, newAuth); + }, + () -> storage.remove(transactionId) + ); + } +} \ No newline at end of file