diff --git a/README.md b/README.md
index 906b357..a7892e5 100644
--- a/README.md
+++ b/README.md
@@ -24,27 +24,23 @@
-
+
### 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