diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 001bc5a..b27c99b 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -68,5 +68,7 @@ jobs:
uses: release-drafter/release-drafter@v5
with:
config-name: release-drafter-config.yml
+ version: ${{ steps.get_version.outputs.VERSION }}
+ tag: v${{ steps.get_version.outputs.VERSION }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 9931ea6..e85d8a2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+/src/main/resources/logback-test.xml
*.java.hsp
*.sonarj
*.sw*
diff --git a/README.md b/README.md
index 367b007..339d904 100644
--- a/README.md
+++ b/README.md
@@ -25,21 +25,14 @@
-
+
-> [!TIP]
-> - **[EasyCodef V2 Wiki](https://github.com/codef-io/easycodef-java-v2/wiki)**
-
-
-
-> [!NOTE]
-> - [Codef Homepage](https://codef.io/)
+> [!IMPORTANT]
+> - **[EasyCodef Java V2 Wiki Guide Docs](https://github.com/codef-io/easycodef-java-v2/wiki)**
> - [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 0316f1e..4a243cb 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -10,7 +10,7 @@ plugins {
group = "io.codef.api"
-version = "2.0.0-beta-003"
+version = "2.0.0-beta-004"
signing {
useInMemoryPgpKeys(
diff --git a/src/main/java/io/codef/api/CodefExecutorManager.java b/src/main/java/io/codef/api/CodefExecutorManager.java
new file mode 100644
index 0000000..5ac77ce
--- /dev/null
+++ b/src/main/java/io/codef/api/CodefExecutorManager.java
@@ -0,0 +1,65 @@
+package io.codef.api;
+
+import io.codef.api.dto.EasyCodefRequest;
+import io.codef.api.dto.EasyCodefResponse;
+import io.codef.api.facade.SingleReqFacade;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+// 실행기 관리를 위한 클래스
+public class CodefExecutorManager implements AutoCloseable {
+ private final ScheduledExecutorService scheduler;
+ private final Executor virtualThreadExecutor;
+
+ private CodefExecutorManager(ScheduledExecutorService scheduler, Executor virtualThreadExecutor) {
+ this.scheduler = scheduler;
+ this.virtualThreadExecutor = virtualThreadExecutor;
+ }
+
+ public static CodefExecutorManager create() {
+ return new CodefExecutorManager(
+ Executors.newScheduledThreadPool(1),
+ Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory())
+ );
+ }
+
+ public CompletableFuture scheduleRequest(
+ EasyCodefRequest request,
+ long delayMs,
+ SingleReqFacade facade
+ ) {
+ return scheduleDelayedExecution(delayMs)
+ .thenComposeAsync(
+ ignored -> executeRequest(request, facade),
+ virtualThreadExecutor
+ );
+ }
+
+ private CompletableFuture scheduleDelayedExecution(long delayMs) {
+ CompletableFuture future = new CompletableFuture<>();
+ scheduler.schedule(
+ () -> future.complete(null),
+ delayMs,
+ TimeUnit.MILLISECONDS
+ );
+ return future;
+ }
+
+ private CompletableFuture executeRequest(
+ EasyCodefRequest request,
+ SingleReqFacade facade
+ ) {
+ return CompletableFuture.supplyAsync(
+ () -> facade.requestProduct(request),
+ virtualThreadExecutor
+ );
+ }
+
+ @Override
+ public void close() {
+ scheduler.shutdown();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/codef/api/EasyCodef.java b/src/main/java/io/codef/api/EasyCodef.java
index 9b9bdab..ebda865 100644
--- a/src/main/java/io/codef/api/EasyCodef.java
+++ b/src/main/java/io/codef/api/EasyCodef.java
@@ -1,289 +1,90 @@
package io.codef.api;
-import static io.codef.api.dto.EasyCodefRequest.SSO_ID;
-import static io.codef.api.dto.EasyCodefRequest.TRUE;
-
-import io.codef.api.constants.CodefClientType;
-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 io.codef.api.facade.MultipleReqFacade;
+import io.codef.api.facade.SimpleAuthCertFacade;
+import io.codef.api.facade.SingleReqFacade;
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.ArrayList;
import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.IntStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EasyCodef {
- private static final long REQUEST_DELAY_MS = 700L;
private static final Logger log = LoggerFactory.getLogger(EasyCodef.class);
- private final SimpleAuthStorage simpleAuthStorage;
- private final MultipleRequestStorage multipleRequestStorage;
+ private final SingleReqFacade singleReqFacade;
+ private final MultipleReqFacade multipleReqFacade;
+ private final SimpleAuthCertFacade simpleAuthCertFacade;
+
private final PublicKey publicKey;
- private final CodefClientType clientType;
- private final EasyCodefToken easyCodefToken;
protected EasyCodef(EasyCodefBuilder builder) {
-
this.publicKey = RsaUtil.generatePublicKey(builder.getPublicKey());
- log.info("EasyCodef RSA public key successfully initialized.");
-
- this.clientType = builder.getClientType();
- log.info("Your Codef clientType {} is successfully initialized.", clientType);
-
- this.easyCodefToken = new EasyCodefToken(builder);
- this.simpleAuthStorage = new SimpleAuthStorage();
- this.multipleRequestStorage = new MultipleRequestStorage();
-
- log.info("==================================================");
- log.info("Your EasyCodef Entity is successfully initialized!");
- log.info("==================================================\n");
- }
-
- private List returnFirstErrorResponse(
- EasyCodefResponse firstResponse,
- String transactionId
- ) {
- log.info("Result Status Code : {}", firstResponse.code());
-
- return List.of(firstResponse);
- }
-
- /**
- * 단일 상품 요청
- */
- public EasyCodefResponse requestProduct(EasyCodefRequest request) throws CodefException {
- String requestUrl = buildRequestUrl(request);
- EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken();
-
- EasyCodefResponse response =
- EasyCodefConnector.requestProduct(request, validToken, requestUrl);
-
- simpleAuthStorage.storeIfRequired(request, response, requestUrl);
- return response;
- }
- /**
- * 다중 상품 요청
- */
- public EasyCodefResponse requestMultipleProduct(List requests)
- throws CodefException {
- validateRequests(requests);
- easyCodefToken.validateAndRefreshToken();
- assignSsoId(requests, UUID.randomUUID().toString());
+ EasyCodefToken easyCodefToken = new EasyCodefToken(builder);
+ SimpleAuthStorage simpleAuthStorage = new SimpleAuthStorage();
+ MultipleRequestStorage multipleRequestStorage = new MultipleRequestStorage();
+ CodefExecutorManager executorManager = CodefExecutorManager.create();
- var executors = createExecutors();
-
- try {
- return processMultipleRequests(requests, executors);
- } finally {
- cleanupExecutors(executors);
- }
- }
-
- /**
- * 단건 간편인증 완료 요청
- */
- public EasyCodefResponse requestSimpleAuthCertification(String transactionId)
- throws CodefException {
- CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId);
- easyCodefToken.validateAndRefreshToken();
-
- EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth);
- EasyCodefResponse response = executeSimpleAuthRequest(enrichedRequest,
- simpleAuth.requestUrl());
-
- simpleAuthStorage.updateIfRequired(
- simpleAuth.requestUrl(),
- enrichedRequest,
- response,
- transactionId
+ this.singleReqFacade = new SingleReqFacade(
+ easyCodefToken,
+ simpleAuthStorage,
+ builder.getClientType()
);
- log.info("Result Status Code : {}", response.code());
-
- return response;
- }
-
- /**
- * 다건 간편인증 완료 요청
- */
- public List requestMultipleSimpleAuthCertification(String transactionId)
- throws CodefException {
- CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId);
- easyCodefToken.validateAndRefreshToken();
-
- EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth);
- EasyCodefResponse firstResponse =
- executeSimpleAuthRequest(enrichedRequest, simpleAuth.requestUrl());
-
- simpleAuthStorage.updateIfRequired(
- simpleAuth.requestUrl(),
- enrichedRequest,
- firstResponse,
- transactionId
+ this.multipleReqFacade = new MultipleReqFacade(
+ singleReqFacade,
+ multipleRequestStorage,
+ executorManager
);
- return isSuccessful(firstResponse)
- ? combineWithRemainingResponses(firstResponse, transactionId) // Case CF-00000
- : returnFirstErrorResponse(firstResponse, transactionId);
- }
-
- // 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);
- }
-
- private boolean isSuccessful(EasyCodefResponse response) {
- return CodefResponseCode.CF_00000.equals(response.code());
- }
-
- private List combineWithRemainingResponses(
- EasyCodefResponse firstResponse,
- String transactionId
- ) throws CodefException {
-
- log.info("remainingResponses called By transactionId `{}`", transactionId);
-
- List remainingResponses =
- multipleRequestStorage.getRemainingResponses(transactionId);
-
- log.info("Await Response Count = {}", remainingResponses.size());
- List allResponses = new ArrayList<>(remainingResponses);
- allResponses.add(firstResponse);
-
- log.info("Total Response Count = {}", allResponses.size());
- log.info("Result Status Codes : {}",
- allResponses.stream().map(EasyCodefResponse::code).toList());
-
- return allResponses;
- }
-
- private void validateRequests(List requests) {
- requests.forEach(
- request -> CodefValidator.requireNonNullElseThrow(request, CodefError.REQUEST_NULL));
- }
-
- 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())
+ this.simpleAuthCertFacade = new SimpleAuthCertFacade(
+ singleReqFacade,
+ simpleAuthStorage,
+ multipleRequestStorage
);
- }
-
- private EasyCodefResponse processMultipleRequests(
- List requests,
- CodefExecutors codefExecutors
- ) throws CodefException {
- List> futures =
- scheduleRequests(requests, codefExecutors);
-
- CompletableFuture firstCompleted =
- CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]))
- .thenApply(result -> (EasyCodefResponse) result);
- EasyCodefResponse result = firstCompleted.join();
- multipleRequestStorage.store(result.transactionId(), futures);
-
- return result;
+ logInitializeSuccessfully();
}
- 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();
+ public EasyCodefResponse requestProduct(EasyCodefRequest request) throws CodefException {
+ return singleReqFacade.requestProduct(request);
}
- private CompletableFuture scheduleRequest(
- EasyCodefRequest request,
- long delayMs,
- CodefExecutors codefExecutors
- ) {
- CompletableFuture future = new CompletableFuture<>();
-
- codefExecutors.scheduler.schedule(
- () -> executeRequest(request, codefExecutors.virtualThreadExecutor, future),
- delayMs,
- TimeUnit.MILLISECONDS
- );
-
- return future;
+ public EasyCodefResponse requestMultipleProduct(List requests) throws CodefException {
+ return multipleReqFacade.requestMultipleProduct(requests);
}
- 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);
- }
- });
+ public EasyCodefResponse requestSimpleAuthCertification(String transactionId) throws CodefException {
+ return simpleAuthCertFacade.requestSimpleAuthCertification(transactionId);
}
- private void cleanupExecutors(CodefExecutors codefExecutors) {
- codefExecutors.scheduler.shutdown();
+ public List requestMultipleSimpleAuthCertification(String transactionId) throws CodefException {
+ return simpleAuthCertFacade.requestMultipleSimpleAuthCertification(transactionId);
}
public PublicKey getPublicKey() {
return publicKey;
}
- private record CodefExecutors(
- ScheduledExecutorService scheduler,
- Executor virtualThreadExecutor
- ) {
-
+ private void logInitializeSuccessfully() {
+ log.info("""
+
+
+ ------. ,-----. ,--. ,---.\s
+ | .---' ,--,--. ,---.,--. ,--.' .--./ ,---. ,-| |,---. / .-'\s
+ | `--, ' ,-. |( .-' \\ ' / | | | .-. |' .-. | .-. :| `-,\s
+ | `---.\\ '-' |.-' `) \\ ' ' '--'\\' '-' '\\ `-' \\ --.| .-'\s
+ `------' `--`--'`----'.-' / `-----' `---' `---' `----'`--' \s
+
+ > EasyCodef v2.0.0-beta-004 Successfully Initialized! Hello worlds!
+ """
+ );
}
}
\ No newline at end of file
diff --git a/src/main/java/io/codef/api/EasyCodefConnector.java b/src/main/java/io/codef/api/EasyCodefConnector.java
index 250a824..82d9d41 100644
--- a/src/main/java/io/codef/api/EasyCodefConnector.java
+++ b/src/main/java/io/codef/api/EasyCodefConnector.java
@@ -1,7 +1,5 @@
package io.codef.api;
-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;
import com.alibaba.fastjson2.JSON;
@@ -11,11 +9,13 @@
import io.codef.api.dto.EasyCodefResponse;
import io.codef.api.error.CodefError;
import io.codef.api.error.CodefException;
-import io.codef.api.logger.JsonLogUtil;
+import io.codef.api.util.AuthorizationUtil;
import io.codef.api.util.HttpClientUtil;
+import io.codef.api.util.JsonUtil;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.slf4j.Logger;
@@ -54,7 +54,8 @@ public static String requestToken(
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));
+ String basicToken = AuthorizationUtil.createBasicAuth(codefOAuthToken);
+ httpPost.addHeader(AUTHORIZATION, basicToken);
return httpPost;
}
@@ -64,8 +65,8 @@ private static HttpPost createProductRequest(
String requestUrl
) {
HttpPost httpPost = new HttpPost(requestUrl);
- httpPost.addHeader(AUTHORIZATION,
- String.format(BEARER_TOKEN_FORMAT, token.getAccessToken()));
+ String accessToken = AuthorizationUtil.createBearerAuth(token.getAccessToken());
+ httpPost.addHeader(AUTHORIZATION, accessToken);
String rawRequest = JSON.toJSONString(request.requestBody());
httpPost.setEntity(new StringEntity(rawRequest, StandardCharsets.UTF_8));
@@ -76,19 +77,13 @@ private static HttpPost createProductRequest(
private static T executeRequest(
HttpPost request,
ResponseProcessor processor
- ) {
- try (var httpClient = HttpClientUtil.createClient()) {
- log.info("[{}] Codef API Request", request.hashCode());
- log.info("> Request Host : {}://{}", request.getScheme(),
- request.getAuthority().toString());
- log.info("> Requset Uri : {}\n", request.getRequestUri());
+ ) throws CodefException {
+ logRequest(request);
+ try (CloseableHttpClient httpClient = HttpClientUtil.createClient()) {
return httpClient.execute(request, response -> {
- log.info("[{}] Codef API Response", request.hashCode());
- log.info("> Response Status : {}", response.getCode());
T result = processor.process(response);
- log.info("> Response →\n{}\n", JsonLogUtil.toPrettyJson(result));
-
+ logResponse(request.hashCode(), response, result);
return result;
});
} catch (IOException exception) {
@@ -96,6 +91,25 @@ private static T executeRequest(
}
}
+ private static void logRequest(HttpPost request) {
+ log.info("[{}] Codef API Request", request.hashCode());
+ log.info("> Request Host: {}://{}",
+ request.getScheme(),
+ request.getAuthority()
+ );
+ log.info("> Request URI: {}\n", request.getRequestUri());
+ }
+
+ private static void logResponse(
+ int requestHashCode,
+ ClassicHttpResponse response,
+ Object result
+ ) {
+ log.info("[{}] Codef API Response", requestHashCode);
+ log.info("> Response Status: {}", response.getCode());
+ log.info("> Response → \n{}\n", JsonUtil.toPrettyJson(result));
+ }
+
@FunctionalInterface
private interface ResponseProcessor {
diff --git a/src/main/java/io/codef/api/EasyCodefToken.java b/src/main/java/io/codef/api/EasyCodefToken.java
index 6eeec8a..13c2d76 100644
--- a/src/main/java/io/codef/api/EasyCodefToken.java
+++ b/src/main/java/io/codef/api/EasyCodefToken.java
@@ -32,7 +32,7 @@ protected EasyCodefToken(EasyCodefBuilder builder) {
this.expiresAt = LocalDateTime.now().plusDays(VALIDITY_PERIOD_DAYS);
log.info(
- "Codef API AccessToken expiry at {} but, EasyCodef will handle automatic renewal",
+ "Codef API AccessToken expiry at {}. Also, EasyCodef will handle automatic renewal.",
expiresAt
);
log.info("Codef API AccessToken successfully initialized.\n");
diff --git a/src/main/java/io/codef/api/ResponseHandler.java b/src/main/java/io/codef/api/ResponseHandler.java
index 87c407e..a65fec0 100644
--- a/src/main/java/io/codef/api/ResponseHandler.java
+++ b/src/main/java/io/codef/api/ResponseHandler.java
@@ -13,45 +13,39 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
+import java.util.function.Function;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
public class ResponseHandler {
-
private static final String UTF_8 = StandardCharsets.UTF_8.toString();
- public ResponseHandler() {
- }
-
/**
* 토큰 응답 처리
*/
public String handleTokenResponse(ClassicHttpResponse response) throws CodefException {
- HttpStatusHandler handler = new HttpStatusHandler<>(
- response.getCode(),
+ return handleHttpResponse(
+ response,
+ this::parseAccessToken,
CodefError.OAUTH_UNAUTHORIZED,
CodefError.OAUTH_INTERNAL_ERROR,
- this::parseAccessToken
+ false
);
-
- return handleHttpResponse(response, handler, false);
}
/**
* 상품 응답 처리
*/
- public EasyCodefResponse handleProductResponse(ClassicHttpResponse response)
- throws CodefException {
- HttpStatusHandler handler = new HttpStatusHandler<>(
- response.getCode(),
+ public EasyCodefResponse handleProductResponse(ClassicHttpResponse response) throws CodefException {
+ return handleHttpResponse(
+ response,
+ this::parseProductResponse,
CodefError.CODEF_API_UNAUTHORIZED,
CodefError.CODEF_API_SERVER_ERROR,
- this::parseProductResponse
+ true
);
-
- return handleHttpResponse(response, handler, true);
}
/**
@@ -59,27 +53,31 @@ public EasyCodefResponse handleProductResponse(ClassicHttpResponse response)
*/
private T handleHttpResponse(
ClassicHttpResponse response,
- HttpStatusHandler handler,
+ Function parser,
+ CodefError unauthorizedError,
+ CodefError defaultError,
boolean requireUrlDecoding
) throws CodefException {
String responseBody = extractResponseBody(response, requireUrlDecoding);
- return handleStatusCode(responseBody, handler);
+
+ return switch (response.getCode()) {
+ case HttpStatus.SC_OK -> parser.apply(responseBody);
+ case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(unauthorizedError, responseBody);
+ default -> throw CodefException.of(defaultError, responseBody);
+ };
}
/**
* HTTP 응답 본문 추출
*/
- private String extractResponseBody(
- ClassicHttpResponse response,
- boolean requiresDecoding
- ) {
+ private String extractResponseBody(ClassicHttpResponse response, boolean requiresDecoding) throws CodefException {
try {
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);
+ } catch (IOException e) {
+ throw CodefException.of(CodefError.IO_ERROR, e);
+ } catch (ParseException e) {
+ throw CodefException.of(CodefError.PARSE_ERROR, e);
}
}
@@ -89,8 +87,8 @@ private String extractResponseBody(
private String parseAccessToken(String responseBody) throws CodefException {
try {
return JSON.parseObject(responseBody).getString(ACCESS_TOKEN);
- } catch (Exception exception) {
- throw CodefException.of(CodefError.PARSE_ERROR, exception);
+ } catch (Exception e) {
+ throw CodefException.of(CodefError.PARSE_ERROR, e);
}
}
@@ -98,50 +96,20 @@ private String parseAccessToken(String responseBody) throws CodefException {
* 상품 응답 파싱
*/
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));
-
- 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);
- };
- }
-
- /**
- * HTTP 응답 처리를 위한 공통 인터페이스
- */
- private interface ResponseParser {
-
- T parse(String responseBody) throws CodefException;
+ try {
+ 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));
- /**
- * HTTP 응답 상태 코드에 따른 처리를 위한 레코드
- */
- private record HttpStatusHandler(
- int statusCode,
- CodefError unauthorizedError,
- CodefError defaultError,
- ResponseParser successHandler
- ) {
+ Object data = Optional.ofNullable(jsonResponse.getJSONObject(DATA))
+ .map(obj -> obj.to(Object.class))
+ .orElseThrow(() -> CodefException.from(CodefError.PARSE_ERROR));
+ return new EasyCodefResponse(result, data);
+ } catch (Exception e) {
+ throw CodefException.of(CodefError.PARSE_ERROR, e);
+ }
}
}
\ No newline at end of file
diff --git a/src/main/java/io/codef/api/constants/CodefResponseCode.java b/src/main/java/io/codef/api/constants/CodefResponseCode.java
index c370d9d..97fd50f 100644
--- a/src/main/java/io/codef/api/constants/CodefResponseCode.java
+++ b/src/main/java/io/codef/api/constants/CodefResponseCode.java
@@ -4,4 +4,5 @@ public class CodefResponseCode {
public static final String CF_00000 = "CF-00000";
public static final String CF_03002 = "CF-03002";
+ public static final String CF_12872 = "CF-12872";
}
diff --git a/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java b/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java
index d86c9c3..42d2124 100644
--- a/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java
+++ b/src/main/java/io/codef/api/dto/CodefTransactionIdResponse.java
@@ -3,8 +3,4 @@
public record CodefTransactionIdResponse(
String transactionId
) {
-
- public static CodefTransactionIdResponse from(String transactionId) {
- return new CodefTransactionIdResponse(transactionId);
- }
}
diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequest.java b/src/main/java/io/codef/api/dto/EasyCodefRequest.java
index 17c7753..2486fb3 100644
--- a/src/main/java/io/codef/api/dto/EasyCodefRequest.java
+++ b/src/main/java/io/codef/api/dto/EasyCodefRequest.java
@@ -1,8 +1,5 @@
package io.codef.api.dto;
-import static org.apache.hc.client5.http.auth.StandardAuthScheme.BASIC;
-import static org.apache.hc.client5.http.auth.StandardAuthScheme.BEARER;
-
import java.util.HashMap;
public record EasyCodefRequest(
@@ -10,12 +7,6 @@ public record EasyCodefRequest(
HashMap requestBody
) {
- /**
- * Header Format Constants
- */
- public static final String BEARER_TOKEN_FORMAT = BEARER + " %s";
- public static final String BASIC_TOKEN_FORMAT = BASIC + " %s";
-
/**
* Header Format Constants
*/
diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java
index 5d3a6aa..24e3d52 100644
--- a/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java
+++ b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java
@@ -84,13 +84,7 @@ public EasyCodefRequest build() {
this.requestBody(EASY_CODEF_JAVA_FLAG, true);
this.generalRequestBody.putAll(secureRequestBody);
- EasyCodefRequest easyCodefRequest = new EasyCodefRequest(path, generalRequestBody);
-
-// log.info("[EasyCodef] request object has been successfully built [ {} ]");
-// log.info(">> path = {}", path);
-// log.info(">> requestBody = {}", generalRequestBody);
-
- return easyCodefRequest;
+ return new EasyCodefRequest(path, generalRequestBody);
}
private void encryptSecureRequestBody() {
diff --git a/src/main/java/io/codef/api/facade/MultipleReqFacade.java b/src/main/java/io/codef/api/facade/MultipleReqFacade.java
new file mode 100644
index 0000000..91ad38d
--- /dev/null
+++ b/src/main/java/io/codef/api/facade/MultipleReqFacade.java
@@ -0,0 +1,76 @@
+package io.codef.api.facade;
+
+import static io.codef.api.dto.EasyCodefRequest.SSO_ID;
+
+import io.codef.api.CodefExecutorManager;
+import io.codef.api.CodefValidator;
+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 java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.IntStream;
+
+// 다중 요청 처리기
+public class MultipleReqFacade {
+ private static final long REQUEST_DELAY_MS = 700L;
+
+ private final SingleReqFacade singleReqFacade;
+ private final MultipleRequestStorage multipleRequestStorage;
+ private final CodefExecutorManager executorManager;
+
+ public MultipleReqFacade(
+ SingleReqFacade singleReqFacade,
+ MultipleRequestStorage multipleRequestStorage,
+ CodefExecutorManager executorManager
+ ) {
+ this.singleReqFacade = singleReqFacade;
+ this.multipleRequestStorage = multipleRequestStorage;
+ this.executorManager = executorManager;
+ }
+
+ public EasyCodefResponse requestMultipleProduct(List requests)
+ throws CodefException {
+ validateRequests(requests);
+ assignSsoId(requests, UUID.randomUUID().toString());
+
+ try {
+ List> futures = scheduleRequests(requests);
+
+ CompletableFuture firstCompleted =
+ CompletableFuture.anyOf(futures.toArray(new CompletableFuture[0]))
+ .thenApply(result -> (EasyCodefResponse) result);
+
+ EasyCodefResponse result = firstCompleted.join();
+ multipleRequestStorage.store(result.transactionId(), futures);
+
+ return result;
+ } finally {
+ executorManager.close();
+ }
+ }
+
+ private void validateRequests(List requests) {
+ requests.forEach(
+ request -> CodefValidator.requireNonNullElseThrow(request, CodefError.REQUEST_NULL));
+ }
+
+ private void assignSsoId(List requests, String uuid) {
+ requests.forEach(request -> request.requestBody().put(SSO_ID, uuid));
+ }
+
+ private List> scheduleRequests(
+ List requests
+ ) {
+ return IntStream.range(0, requests.size())
+ .mapToObj(i -> executorManager.scheduleRequest(
+ requests.get(i),
+ i * REQUEST_DELAY_MS,
+ singleReqFacade
+ ))
+ .toList();
+ }
+}
diff --git a/src/main/java/io/codef/api/facade/SimpleAuthCertFacade.java b/src/main/java/io/codef/api/facade/SimpleAuthCertFacade.java
new file mode 100644
index 0000000..e4e6253
--- /dev/null
+++ b/src/main/java/io/codef/api/facade/SimpleAuthCertFacade.java
@@ -0,0 +1,155 @@
+package io.codef.api.facade;
+
+import static io.codef.api.constants.CodefResponseCode.CF_03002;
+import static io.codef.api.constants.CodefResponseCode.CF_12872;
+import static io.codef.api.dto.EasyCodefRequest.TRUE;
+
+import com.alibaba.fastjson2.JSON;
+import io.codef.api.constants.CodefResponseCode;
+import io.codef.api.dto.EasyCodefRequest;
+import io.codef.api.dto.EasyCodefResponse;
+import io.codef.api.error.CodefException;
+import io.codef.api.storage.MultipleRequestStorage;
+import io.codef.api.storage.SimpleAuthStorage;
+import io.codef.api.vo.CodefSimpleAuth;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SimpleAuthCertFacade {
+
+ private static final Logger log = LoggerFactory.getLogger(SimpleAuthCertFacade.class);
+
+ private final SingleReqFacade singleReqFacade;
+ private final SimpleAuthStorage simpleAuthStorage;
+ private final MultipleRequestStorage multipleRequestStorage;
+
+ public SimpleAuthCertFacade(
+ SingleReqFacade singleReqFacade,
+ SimpleAuthStorage simpleAuthStorage,
+ MultipleRequestStorage multipleRequestStorage
+ ) {
+ this.singleReqFacade = singleReqFacade;
+ this.simpleAuthStorage = simpleAuthStorage;
+ this.multipleRequestStorage = multipleRequestStorage;
+ }
+
+ public EasyCodefResponse requestSimpleAuthCertification(String transactionId) throws CodefException {
+ CodefSimpleAuth simpleAuth = simpleAuthStorage.get(transactionId);
+ EasyCodefRequest enrichedRequest = enrichRequestWithTwoWayInfo(simpleAuth);
+ EasyCodefResponse response = singleReqFacade.requestProduct(enrichedRequest);
+
+ simpleAuthStorage.updateIfRequired(
+ simpleAuth.requestUrl(),
+ enrichedRequest,
+ response,
+ transactionId
+ );
+
+ logResponseStatus(response, transactionId);
+ return response;
+ }
+
+ public List requestMultipleSimpleAuthCertification(
+ String transactionId
+ ) throws CodefException {
+ EasyCodefResponse firstResponse = requestSimpleAuthCertification(transactionId);
+
+ return isSuccessResponse(firstResponse)
+ ? combineWithRemainingResponses(firstResponse, transactionId)
+ : returnFirstResponse(firstResponse);
+ }
+
+ private void logAddAuthResponseStatus(
+ EasyCodefResponse response,
+ String transactionId,
+ String resultStatusCode
+ ) {
+ if (resultStatusCode.equals(CF_03002)) {
+ Object data = response.data();
+ String addAuthMethod = JSON.parseObject(data.toString()).getString("method");
+
+ log.warn("Additional authentication required | method : {}\n", addAuthMethod);
+ } else if (resultStatusCode.equals(CF_12872)) {
+ log.warn(
+ "Retry limit for additional authentication exceeded. "
+ + "Please restart the process from the initial request.\n"
+ );
+ }
+ }
+
+ private void logDefaultResponseStatus(String transactionId, String resultStatusCode) {
+ log.info("Result Status Code : {}", resultStatusCode);
+ log.info("Transaction Id : {}", transactionId);
+ }
+
+ private void logResponseStatus(
+ EasyCodefResponse response,
+ String transactionId
+ ) {
+ String resultStatusCode = response.code();
+ logDefaultResponseStatus(transactionId, resultStatusCode);
+
+ logAddAuthResponseStatus(response, transactionId, resultStatusCode);
+ }
+
+ private List returnFirstResponse(EasyCodefResponse firstErrorResponse) {
+ return List.of(firstErrorResponse);
+ }
+
+ private boolean isSuccessResponse(EasyCodefResponse response) {
+ return CodefResponseCode.CF_00000.equals(response.code());
+ }
+
+ private boolean isAddAuthResponse(EasyCodefResponse response) {
+ return CodefResponseCode.CF_03002.equals(response.code());
+ }
+
+ private boolean isFailureResponse(EasyCodefResponse response) {
+ return !isSuccessResponse(response) && !isAddAuthResponse(response);
+ }
+
+ 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 List combineWithRemainingResponses(EasyCodefResponse firstResponse,
+ String transactionId) throws CodefException {
+
+ log.info("Await Responses called By transactionId `{}`", transactionId);
+ List responses = multipleRequestStorage.getRemainingResponses(
+ transactionId);
+ log.info("Await Responses Count = {}", responses.size());
+
+ responses.add(firstResponse);
+ List allResponseCodes = responses.stream().map(EasyCodefResponse::code).toList();
+ log.info("Total Responses Count = {}\n", responses.size());
+
+ long successCount = responses.stream().filter(this::isSuccessResponse).count();
+ long addAuthCount = responses.stream().filter(this::isAddAuthResponse).count();
+ long failureCount = responses.stream().filter(this::isFailureResponse).count();
+
+ log.info("Success Response Status [CF-00000] Count : {}", successCount);
+ log.info("AddAuth Response Status [CF-03002] Count : {}", addAuthCount);
+ log.warn("Failure Response Status [ Else ] Count : {}", failureCount);
+
+ if (failureCount > 0) {
+ responses.stream()
+ .filter(this::isFailureResponse)
+ .map(EasyCodefResponse::code)
+ .collect(Collectors.groupingBy(code -> code, Collectors.counting()))
+ .forEach((code, count) -> log.warn("> Error code : {}, Count: {}", code, count));
+ }
+
+ return responses;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/codef/api/facade/SingleReqFacade.java b/src/main/java/io/codef/api/facade/SingleReqFacade.java
new file mode 100644
index 0000000..c0d1ebf
--- /dev/null
+++ b/src/main/java/io/codef/api/facade/SingleReqFacade.java
@@ -0,0 +1,41 @@
+package io.codef.api.facade;
+
+import io.codef.api.EasyCodefConnector;
+import io.codef.api.EasyCodefToken;
+import io.codef.api.constants.CodefClientType;
+import io.codef.api.dto.EasyCodefRequest;
+import io.codef.api.dto.EasyCodefResponse;
+import io.codef.api.error.CodefException;
+import io.codef.api.storage.SimpleAuthStorage;
+
+// 단일 요청 처리기
+public class SingleReqFacade {
+ private final EasyCodefToken easyCodefToken;
+ private final SimpleAuthStorage simpleAuthStorage;
+ private final CodefClientType clientType;
+
+ public SingleReqFacade(
+ EasyCodefToken easyCodefToken,
+ SimpleAuthStorage simpleAuthStorage,
+ CodefClientType clientType
+ ) {
+ this.easyCodefToken = easyCodefToken;
+ this.simpleAuthStorage = simpleAuthStorage;
+ this.clientType = clientType;
+ }
+
+ public EasyCodefResponse requestProduct(EasyCodefRequest request) throws CodefException {
+ String requestUrl = buildRequestUrl(request);
+ EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken();
+
+ EasyCodefResponse response =
+ EasyCodefConnector.requestProduct(request, validToken, requestUrl);
+
+ simpleAuthStorage.storeIfRequired(request, response, requestUrl);
+ return response;
+ }
+
+ private String buildRequestUrl(EasyCodefRequest request) {
+ return clientType.getHost() + request.path();
+ }
+}
diff --git a/src/main/java/io/codef/api/logger/JsonLogUtil.java b/src/main/java/io/codef/api/logger/JsonLogUtil.java
deleted file mode 100644
index 08b36ff..0000000
--- a/src/main/java/io/codef/api/logger/JsonLogUtil.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.codef.api.logger;
-
-import com.alibaba.fastjson2.JSON;
-import com.alibaba.fastjson2.JSONWriter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class JsonLogUtil {
-
- private static final Logger log = LoggerFactory.getLogger(JsonLogUtil.class);
-
- public static String toPrettyJson(Object obj) {
- return JSON.toJSONString(obj,
- JSONWriter.Feature.PrettyFormat,
- JSONWriter.Feature.WriteMapNullValue,
- JSONWriter.Feature.WriteNullListAsEmpty
- );
- }
-}
\ No newline at end of file
diff --git a/src/main/java/io/codef/api/storage/MultipleRequestStorage.java b/src/main/java/io/codef/api/storage/MultipleRequestStorage.java
index 5baeb46..069b14b 100644
--- a/src/main/java/io/codef/api/storage/MultipleRequestStorage.java
+++ b/src/main/java/io/codef/api/storage/MultipleRequestStorage.java
@@ -4,11 +4,13 @@
import io.codef.api.dto.EasyCodefResponse;
import io.codef.api.error.CodefError;
import io.codef.api.error.CodefException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
public class MultipleRequestStorage {
@@ -30,7 +32,7 @@ public List getRemainingResponses(
.map(this::safeJoin)
.filter(Objects::nonNull)
.filter(response -> !Objects.equals(response.transactionId(), transactionId))
- .toList();
+ .collect(Collectors.toCollection(ArrayList::new));
storage.remove(transactionId);
return results;
diff --git a/src/main/java/io/codef/api/storage/SimpleAuthStorage.java b/src/main/java/io/codef/api/storage/SimpleAuthStorage.java
index 0be9371..edaaf9e 100644
--- a/src/main/java/io/codef/api/storage/SimpleAuthStorage.java
+++ b/src/main/java/io/codef/api/storage/SimpleAuthStorage.java
@@ -1,11 +1,11 @@
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 io.codef.api.vo.CodefSimpleAuth;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
diff --git a/src/main/java/io/codef/api/util/AuthorizationUtil.java b/src/main/java/io/codef/api/util/AuthorizationUtil.java
new file mode 100644
index 0000000..1ffe625
--- /dev/null
+++ b/src/main/java/io/codef/api/util/AuthorizationUtil.java
@@ -0,0 +1,14 @@
+package io.codef.api.util;
+
+public class AuthorizationUtil {
+ private static final String BASIC_FORMAT = "Basic %s";
+ private static final String BEARER_FORMAT = "Bearer %s";
+
+ public static String createBasicAuth(String token) {
+ return String.format(BASIC_FORMAT, token);
+ }
+
+ public static String createBearerAuth(String token) {
+ return String.format(BEARER_FORMAT, token);
+ }
+}
diff --git a/src/main/java/io/codef/api/util/JsonUtil.java b/src/main/java/io/codef/api/util/JsonUtil.java
new file mode 100644
index 0000000..e59796e
--- /dev/null
+++ b/src/main/java/io/codef/api/util/JsonUtil.java
@@ -0,0 +1,15 @@
+package io.codef.api.util;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONWriter;
+
+public class JsonUtil {
+
+ public static String toPrettyJson(Object object) {
+ return JSON.toJSONString(object,
+ JSONWriter.Feature.PrettyFormat,
+ JSONWriter.Feature.WriteMapNullValue,
+ JSONWriter.Feature.WriteNullListAsEmpty
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/codef/api/dto/CodefSimpleAuth.java b/src/main/java/io/codef/api/vo/CodefSimpleAuth.java
similarity index 52%
rename from src/main/java/io/codef/api/dto/CodefSimpleAuth.java
rename to src/main/java/io/codef/api/vo/CodefSimpleAuth.java
index 3000e92..bac6e45 100644
--- a/src/main/java/io/codef/api/dto/CodefSimpleAuth.java
+++ b/src/main/java/io/codef/api/vo/CodefSimpleAuth.java
@@ -1,4 +1,7 @@
-package io.codef.api.dto;
+package io.codef.api.vo;
+
+import io.codef.api.dto.EasyCodefRequest;
+import io.codef.api.dto.EasyCodefResponse;
public record CodefSimpleAuth(
String requestUrl,