From 608dd66574abd6dd8c4a917c8618f293f7a2e3c2 Mon Sep 17 00:00:00 2001 From: h-beeen Date: Wed, 13 Nov 2024 15:45:50 +0900 Subject: [PATCH 1/5] docs: update README.md --- README.md | 76 +++++++++++++++---------------------------------------- 1 file changed, 21 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 9af3b91..bf59c0b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

EasyCodef Java V2

+

@@ -11,71 +11,37 @@
-`easycodef-java-v2`는 codef API를 JDK 환경에서 더욱 더 편리하게 연동할 수 있도록 돕는 오픈소스 라이브러리입니다. - -현재 알파 버전 개발중으로 v2.0.0-ALPHA-002 버전으로 Maven Central Repository를 통해 배포중입니다. - -2024년 상반기 실제 고객사 대상으로 릴리즈 예정입니다. - -

+

+ easycodef-java-v2 - Open-Source library
With the CODEF API in a JDK environment
+

-## Release +
-[![Build Status](https://img.shields.io/github/actions/workflow/status/codef-io/easycodef-java-v2/publish.yml?style=for-the-badge&logo=gradle&color=02303A)](https://github.com/codef-io/easycodef-java-v2/actions?query=branch%3Amaster)
-[![Last Commit](https://img.shields.io/github/last-commit/codef-io/easycodef-java-v2/master?style=for-the-badge&label=LAST%20BUILD&logo=Github&color=181717)](https://github.com/codef-io/easycodef-java-v2)
-[![Maven Central](https://img.shields.io/maven-central/v/io.codef.api/easycodef-java-v2.svg?style=for-the-badge&label=Maven%20Central&logo=apache-maven&color=C71A36)](https://central.sonatype.com/artifact/io.codef.api/easycodef-java-v2)
+

+ Build Status + Last Commit + Maven Central +

-## Snippets -- Gradle(Kotlin) - ```gradle - implementation("io.codef.api:easycodef-java-v2:2.0.0-alpha-003") - ``` - -- Gradle(short) - ```gradle - implementation 'io.codef.api:easycodef-java-v2:2.0.0-alpha-003' - ``` - -- Maven - ```xml - - io.codef.api - easycodef-java-v2 - 2.0.0-alpha-003 - - ``` +


-## Get It ! +### EasyCodef V2 For Java +- **[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/) -- 예제 코드 - ```java - EasyCodef easyCodef = EasyCodefBuilder.builder() - .clientType(CodefClientType.DEMO) - .clientId("your-client-id") - .clientSecret("your-client-secret") - .publicKey("your-public-key") - .build(); - - EasyCodefRequest request = EasyCodefRequestBuilder.builder() - .path("/v1/kr/public/hw/nip-cdc-list/my-vaccination") - .organization("0011") - .requestBody("loginType", "1") - .requestBody("userId", "your-nhis-id") - .secureRequestBody("userPassword", "your-nhis-password") - .secureWith(easyCodef) - .build(); - - EasyCodefResponse easyCodefResponse = easyCodef.requestProduct(request); - - final EasyCodefResponse.Result result = easyCodefResponse.result(); - final Object data = easyCodefResponse.data(); - ``` +
--- +
+

헥토데이터
MIT © | LICENSE

+
From ea56a97a69eb0c610b51aa75083ae71e77176add Mon Sep 17 00:00:00 2001 From: h-beeen Date: Wed, 13 Nov 2024 16:51:36 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20=EA=B8=B0=EC=A1=B4=20requestPro?= =?UTF-8?q?duct=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 +- .../java/io/codef/api/CodefValidator.java | 2 +- src/main/java/io/codef/api/EasyCodef.java | 5 +- .../java/io/codef/api/EasyCodefConnector.java | 43 ++++++++------- .../io/codef/api/dto/EasyCodefRequest.java | 19 +++++++ .../api/dto/EasyCodefRequestBuilder.java | 53 ++++++++++--------- .../io/codef/api/dto/EasyCodefResponse.java | 2 + .../java/io/codef/api/error/CodefError.java | 14 ++++- .../io/codef/api/error/CodefException.java | 12 +++++ 9 files changed, 103 insertions(+), 49 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bab98e5..40f2ff8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,7 +89,7 @@ dependencies { * https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j2-impl */ implementation("org.apache.httpcomponents.client5:httpclient5:5.4.1") - testImplementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.1") + implementation("org.apache.logging.log4j:log4j-slf4j2-impl:2.24.1") /** * 2024-10-21 Latest diff --git a/src/main/java/io/codef/api/CodefValidator.java b/src/main/java/io/codef/api/CodefValidator.java index 757237a..de3cc04 100644 --- a/src/main/java/io/codef/api/CodefValidator.java +++ b/src/main/java/io/codef/api/CodefValidator.java @@ -6,7 +6,7 @@ import java.util.Optional; import java.util.UUID; -final class CodefValidator { +public final class CodefValidator { private CodefValidator() { } diff --git a/src/main/java/io/codef/api/EasyCodef.java b/src/main/java/io/codef/api/EasyCodef.java index afb505e..e65e596 100644 --- a/src/main/java/io/codef/api/EasyCodef.java +++ b/src/main/java/io/codef/api/EasyCodef.java @@ -23,9 +23,8 @@ protected EasyCodef( public EasyCodefResponse requestProduct(EasyCodefRequest request) { final String requestUrl = generateRequestUrl(request); - easyCodefToken.validateAndRefreshToken(); - - return EasyCodefConnector.requestProduct(request, easyCodefToken, requestUrl); + final EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); + return EasyCodefConnector.requestProduct(request, validToken, requestUrl); } private String generateRequestUrl(EasyCodefRequest request) { diff --git a/src/main/java/io/codef/api/EasyCodefConnector.java b/src/main/java/io/codef/api/EasyCodefConnector.java index 0cce18a..2598314 100644 --- a/src/main/java/io/codef/api/EasyCodefConnector.java +++ b/src/main/java/io/codef/api/EasyCodefConnector.java @@ -11,40 +11,38 @@ import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; -import static org.apache.hc.client5.http.auth.StandardAuthScheme.BASIC; -import static org.apache.hc.client5.http.auth.StandardAuthScheme.BEARER; +import static io.codef.api.dto.EasyCodefRequest.*; +import static io.codef.api.dto.EasyCodefResponse.DATA; +import static io.codef.api.dto.EasyCodefResponse.RESULT; import static org.apache.hc.core5.http.HttpHeaders.AUTHORIZATION; final class EasyCodefConnector { static String issueToken(String codefOAuthToken) { - System.out.println("issue Token !!!\n\n"); - final String BASIC_TOKEN_FORMAT = BASIC + " %s"; - final String accessTokenParameter = "access_token"; - try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN); httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken)); return httpClient.execute(httpPost, response -> { + final String responseBody = EntityUtils.toString(response.getEntity()); + switch (response.getCode()) { case 200: break; case 401: - throw CodefException.from(CodefError.OAUTH_UNAUTHORIZED); - case 500: + throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, responseBody); default: - throw CodefException.from(CodefError.OAUTH_INTERNAL_ERROR); + throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, responseBody); } - String httpResponse = EntityUtils.toString(response.getEntity()); - - return JSON.parseObject(httpResponse).getString(accessTokenParameter); + return JSON.parseObject(responseBody).getString(ACCESS_TOKEN); }); } catch (CodefException exception) { throw exception; @@ -58,8 +56,6 @@ static EasyCodefResponse requestProduct( EasyCodefToken token, String requestUrl ) { - final String BEARER_TOKEN_FORMAT = BEARER + " %s"; - try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { HttpPost httpPost = new HttpPost(requestUrl); httpPost.addHeader(AUTHORIZATION, String.format(BEARER_TOKEN_FORMAT, token.getAccessToken())); @@ -68,19 +64,28 @@ static EasyCodefResponse requestProduct( return httpClient.execute(httpPost, response -> { String httpResponse = EntityUtils.toString(response.getEntity()); - String decodedResponse = URLDecoder.decode(httpResponse, "UTF-8"); + String decodedResponse = URLDecoder.decode(httpResponse, StandardCharsets.UTF_8); + + final int responseStatusCode = response.getCode(); + + if (responseStatusCode != HttpStatus.SC_OK) { + throw CodefException.of(CodefError.CODEF_API_SERVER_ERROR, decodedResponse); + } - // TODO {"error":"invalid_token","error_description":"Cannot convert access token to JSON","code":"CF-09990","message":"OAUTH2.0 토큰 에러입니다. 메시지를 확인하세요."} JSONObject jsonResponseObject = JSON.parseObject(decodedResponse); - EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject("result").to(EasyCodefResponse.Result.class); - Object dataResponse = jsonResponseObject.getJSONObject("data").to(Object.class); + EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject(RESULT) + .to(EasyCodefResponse.Result.class); + + Object dataResponse = jsonResponseObject.getJSONObject(DATA) + .to(Object.class); + return new EasyCodefResponse(resultResponse, dataResponse); }); } catch (CodefException exception) { throw exception; } catch (Exception exception) { - throw CodefException.of(CodefError.OAUTH_CONNECTION_ERROR, exception); + throw CodefException.of(CodefError.INTERNAL_SERVER_ERROR, exception); } } } diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequest.java b/src/main/java/io/codef/api/dto/EasyCodefRequest.java index 11319b3..c7b0d1e 100644 --- a/src/main/java/io/codef/api/dto/EasyCodefRequest.java +++ b/src/main/java/io/codef/api/dto/EasyCodefRequest.java @@ -2,8 +2,27 @@ import java.util.HashMap; +import static org.apache.hc.client5.http.auth.StandardAuthScheme.BASIC; +import static org.apache.hc.client5.http.auth.StandardAuthScheme.BEARER; + public record EasyCodefRequest( String path, HashMap requestParams ) { + /** + * Header Format Constants + */ + public static final String BEARER_TOKEN_FORMAT = BEARER + " %s"; + public static final String BASIC_TOKEN_FORMAT = BASIC + " %s"; + + /** + * Header Format Constants + */ + public static final String ACCESS_TOKEN = "access_token"; + + /** + * Header Format Constants + */ + public static final String ORGANIZATION = "organization"; + public static final String EASY_CODEF_JAVA_FLAG = "easyCodefJavaV2"; } \ No newline at end of file diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java index 509d793..d9c4a5a 100644 --- a/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java +++ b/src/main/java/io/codef/api/dto/EasyCodefRequestBuilder.java @@ -1,11 +1,17 @@ package io.codef.api.dto; +import io.codef.api.CodefValidator; import io.codef.api.EasyCodef; import io.codef.api.error.CodefError; import io.codef.api.error.CodefException; import io.codef.api.util.RsaUtil; import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static io.codef.api.dto.EasyCodefRequest.EASY_CODEF_JAVA_FLAG; +import static io.codef.api.dto.EasyCodefRequest.ORGANIZATION; public class EasyCodefRequestBuilder { @@ -24,16 +30,18 @@ public static EasyCodefRequestBuilder builder() { } public EasyCodefRequestBuilder organization(Object value) { - generalRequestBody.put("organization", value); + CodefValidator.requireNonNullElseThrow(value, CodefError.NULL_ORGANIZATION); + generalRequestBody.put(ORGANIZATION, value); return this; } public EasyCodefRequestBuilder path(String path) { this.path = path; - if(!path.startsWith("/v1")) { - throw CodefException.from(CodefError.INVALID_PATH_REQUESTED); - } + Optional.of(path) + .filter(p -> p.startsWith("/v1")) + .orElseThrow(() -> CodefException.from(CodefError.INVALID_PATH_REQUESTED)); + return this; } @@ -59,30 +67,27 @@ public EasyCodefRequestBuilder secureWith(EasyCodef easyCodef) { } public EasyCodefRequest build() { - final HashMap requests = new HashMap<>(); - final String EASY_CODEF_JAVA_FLAG = "easyCodefJavaV2"; - - if (!secureRequestBody.isEmpty()) { - if (easyCodef == null) { - throw CodefException.from(CodefError.NEED_TO_SECURE_WITH_METHOD); - } else { - secureRequestBody.forEach((key, value) -> { - String encryptedValue = RsaUtil.encryptRSA(value, easyCodef.getPublicKey()); - secureRequestBody.put(key, encryptedValue); - }); - } - } + CodefValidator.requireNonNullElseThrow(path, CodefError.NEED_TO_PATH_METHOD); + CodefValidator.requireNonNullElseThrow(generalRequestBody.get(ORGANIZATION), CodefError.NEED_TO_ORGANIZATION_METHOD); - if (path == null) { - throw CodefException.from(CodefError.NEED_TO_PATH_METHOD); - } - - if (generalRequestBody.get("organization") == null) { - throw CodefException.from(CodefError.NEED_TO_ORGANIZATION_METHOD); - } + encryptSecureRequestBody(); this.requestBody(EASY_CODEF_JAVA_FLAG, true); this.generalRequestBody.putAll(secureRequestBody); + return new EasyCodefRequest(path, generalRequestBody); } + + private void encryptSecureRequestBody() { + Optional.of(secureRequestBody) + .filter(body -> !body.isEmpty()) + .ifPresent(body -> { + CodefValidator.requireNonNullElseThrow(easyCodef, CodefError.NEED_TO_SECURE_WITH_METHOD); + encryptRequestBodyValues(body); + }); + } + + private void encryptRequestBodyValues(Map body) { + body.replaceAll((key, value) -> RsaUtil.encryptRSA(value, easyCodef.getPublicKey())); + } } diff --git a/src/main/java/io/codef/api/dto/EasyCodefResponse.java b/src/main/java/io/codef/api/dto/EasyCodefResponse.java index 2a91a08..c5ecc37 100644 --- a/src/main/java/io/codef/api/dto/EasyCodefResponse.java +++ b/src/main/java/io/codef/api/dto/EasyCodefResponse.java @@ -4,6 +4,8 @@ public record EasyCodefResponse( Result result, Object data ) { + public static final String RESULT = "result"; + public static final String DATA = "data"; public record Result( String code, diff --git a/src/main/java/io/codef/api/error/CodefError.java b/src/main/java/io/codef/api/error/CodefError.java index b99c9de..ce826a8 100644 --- a/src/main/java/io/codef/api/error/CodefError.java +++ b/src/main/java/io/codef/api/error/CodefError.java @@ -31,18 +31,26 @@ public enum CodefError { "clientType is required and cannot be null.", CodefReferenceUrl.KEY ), + NULL_ORGANIZATION( + "organization is required and cannot be null.", + CodefReferenceUrl.DEV_GUIDE_REST_API + ), OAUTH_UNAUTHORIZED( "Failed to authenticate with the Codef OAuth server (401 Unauthorized). Please verify your clientId and clientSecret values.", CodefReferenceUrl.KEY ), OAUTH_INTERNAL_ERROR( - "An error occurred on the Codef OAuth server (500 Internal Server Error). Please try again later, or contact support if the issue persists.", + "An error occurred on the Codef OAuth server. Please try again later, or contact support if the issue persists.", CodefReferenceUrl.KEY ), OAUTH_CONNECTION_ERROR( "The connection to the OAUTH server failed. Please check if `https://oauth.codef.io` is accessible.", CodefReferenceUrl.DEV_GUIDE_REST_API ), + CODEF_API_SERVER_ERROR( + "An error occurred during the request codef API Product. Please refer to the error message for details.", + CodefReferenceUrl.DEV_GUIDE_REST_API + ), RSA_ENCRYPTION_ERROR( "An error occurred on RSA Encryption. Please check your publicKey", CodefReferenceUrl.KEY @@ -62,6 +70,10 @@ public enum CodefError { INVALID_PATH_REQUESTED( "The path should be requested in the following format: `/v1/kr/***/***/...`", CodefReferenceUrl.PRODUCT + ), + INTERNAL_SERVER_ERROR( + "An error occurred on your request.", + CodefReferenceUrl.DEV_GUIDE_REST_API ); private final String message; diff --git a/src/main/java/io/codef/api/error/CodefException.java b/src/main/java/io/codef/api/error/CodefException.java index 75a98b1..c708e33 100644 --- a/src/main/java/io/codef/api/error/CodefException.java +++ b/src/main/java/io/codef/api/error/CodefException.java @@ -16,6 +16,11 @@ private CodefException(CodefError codefError, Exception exception) { this.codefError = codefError; } + private CodefException(CodefError codefError, String extraMessage) { + super(codefError.getMessage() + '\n' + extraMessage); + this.codefError = codefError; + } + private CodefException(CodefError codefError) { super(codefError.getMessage() + '\n'); this.codefError = codefError; @@ -31,4 +36,11 @@ public static CodefException of( ) { return new CodefException(codefError, exception); } + + public static CodefException of( + CodefError codefError, + String extraMessage + ) { + return new CodefException(codefError, extraMessage); + } } From 9b01dcbe0ce83db8379db4c6d7fc75c2de61157c Mon Sep 17 00:00:00 2001 From: h-beeen Date: Wed, 13 Nov 2024 17:09:57 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=ED=95=A8=EC=88=98=ED=98=95=20?= =?UTF-8?q?=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=ED=99=9C=EC=9A=A9=ED=95=9C=20http=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/codef/api/CodefValidator.java | 3 +- src/main/java/io/codef/api/EasyCodef.java | 11 +- .../java/io/codef/api/EasyCodefConnector.java | 139 +++++++++++------- .../java/io/codef/api/EasyCodefToken.java | 4 +- .../api/constants/CodefReferenceUrl.java | 3 +- .../io/codef/api/dto/EasyCodefRequest.java | 2 +- .../java/io/codef/api/error/CodefError.java | 10 +- .../io/codef/api/util/HttpClientUtil.java | 13 ++ src/main/java/io/codef/api/util/RsaUtil.java | 3 + 9 files changed, 123 insertions(+), 65 deletions(-) create mode 100644 src/main/java/io/codef/api/util/HttpClientUtil.java diff --git a/src/main/java/io/codef/api/CodefValidator.java b/src/main/java/io/codef/api/CodefValidator.java index de3cc04..30ab945 100644 --- a/src/main/java/io/codef/api/CodefValidator.java +++ b/src/main/java/io/codef/api/CodefValidator.java @@ -6,8 +6,7 @@ import java.util.Optional; import java.util.UUID; -public final class CodefValidator { - +public class CodefValidator { private CodefValidator() { } diff --git a/src/main/java/io/codef/api/EasyCodef.java b/src/main/java/io/codef/api/EasyCodef.java index e65e596..62711d8 100644 --- a/src/main/java/io/codef/api/EasyCodef.java +++ b/src/main/java/io/codef/api/EasyCodef.java @@ -3,6 +3,7 @@ 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.util.RsaUtil; import java.security.PublicKey; @@ -21,16 +22,14 @@ protected EasyCodef( this.easyCodefToken = easyCodefToken; } - public EasyCodefResponse requestProduct(EasyCodefRequest request) { - final String requestUrl = generateRequestUrl(request); + public EasyCodefResponse requestProduct( + EasyCodefRequest request + ) throws CodefException { + final String requestUrl = clientType.getHost() + request.path(); final EasyCodefToken validToken = easyCodefToken.validateAndRefreshToken(); return EasyCodefConnector.requestProduct(request, validToken, requestUrl); } - private String generateRequestUrl(EasyCodefRequest request) { - return clientType.getHost() + request.path(); - } - public PublicKey getPublicKey() { return publicKey; } diff --git a/src/main/java/io/codef/api/EasyCodefConnector.java b/src/main/java/io/codef/api/EasyCodefConnector.java index 2598314..b9359db 100644 --- a/src/main/java/io/codef/api/EasyCodefConnector.java +++ b/src/main/java/io/codef/api/EasyCodefConnector.java @@ -1,5 +1,6 @@ package io.codef.api; + import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import io.codef.api.constants.CodefHost; @@ -8,13 +9,16 @@ import io.codef.api.dto.EasyCodefResponse; import io.codef.api.error.CodefError; 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.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +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; import org.apache.hc.core5.http.io.entity.StringEntity; +import java.io.IOException; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; @@ -23,69 +27,100 @@ import static io.codef.api.dto.EasyCodefResponse.RESULT; import static org.apache.hc.core5.http.HttpHeaders.AUTHORIZATION; -final class EasyCodefConnector { - - static String issueToken(String codefOAuthToken) { - try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - HttpPost httpPost = new HttpPost(CodefHost.CODEF_OAUTH_SERVER + CodefPath.ISSUE_TOKEN); - httpPost.addHeader(AUTHORIZATION, String.format(BASIC_TOKEN_FORMAT, codefOAuthToken)); - - return httpClient.execute(httpPost, response -> { - final String responseBody = EntityUtils.toString(response.getEntity()); - - switch (response.getCode()) { - case 200: - break; - case 401: - throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, responseBody); - default: - throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, responseBody); - } - - return JSON.parseObject(responseBody).getString(ACCESS_TOKEN); - }); - } catch (CodefException exception) { - throw exception; - } catch (Exception exception) { - throw CodefException.of(CodefError.OAUTH_CONNECTION_ERROR, exception); - } +@FunctionalInterface +interface ResponseProcessor { + T process(ClassicHttpResponse response) throws IOException, ParseException; +} + +public final class EasyCodefConnector { + private static final ResponseHandler responseHandler = new ResponseHandler(); + + private EasyCodefConnector() { + throw new IllegalStateException("Utility class"); + } + + /** + * OAuth 토큰을 발급받습니다. + */ + public static String requestToken(String codefOAuthToken) { + HttpPost request = createTokenRequest(codefOAuthToken); + return executeRequest(request, responseHandler::handleTokenResponse); } - static EasyCodefResponse requestProduct( + /** + * CODEF 상품 API를 호출합니다. + */ + public static EasyCodefResponse requestProduct( EasyCodefRequest request, EasyCodefToken token, String requestUrl ) { - try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - HttpPost httpPost = new HttpPost(requestUrl); - httpPost.addHeader(AUTHORIZATION, String.format(BEARER_TOKEN_FORMAT, token.getAccessToken())); - String rawRequest = JSON.toJSONString(request.requestParams()); - httpPost.setEntity(new StringEntity(rawRequest)); + HttpPost httpRequest = createProductRequest(request, token, requestUrl); + return executeRequest(httpRequest, responseHandler::handleProductResponse); + } - return httpClient.execute(httpPost, response -> { - String httpResponse = EntityUtils.toString(response.getEntity()); - String decodedResponse = URLDecoder.decode(httpResponse, StandardCharsets.UTF_8); + 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; + } - final int responseStatusCode = response.getCode(); + private static HttpPost createProductRequest( + EasyCodefRequest request, + EasyCodefToken token, + String requestUrl + ) { + HttpPost httpPost = new HttpPost(requestUrl); + httpPost.addHeader(AUTHORIZATION, String.format(BEARER_TOKEN_FORMAT, token.getAccessToken())); - if (responseStatusCode != HttpStatus.SC_OK) { - throw CodefException.of(CodefError.CODEF_API_SERVER_ERROR, decodedResponse); - } + String rawRequest = JSON.toJSONString(request.requestBody()); + httpPost.setEntity(new StringEntity(rawRequest, StandardCharsets.UTF_8)); - JSONObject jsonResponseObject = JSON.parseObject(decodedResponse); + return httpPost; + } - EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject(RESULT) - .to(EasyCodefResponse.Result.class); + private static T executeRequest(HttpPost request, ResponseProcessor processor) { + try (CloseableHttpClient 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); + } + } +} - Object dataResponse = jsonResponseObject.getJSONObject(DATA) - .to(Object.class); +final class ResponseHandler { + String handleTokenResponse(ClassicHttpResponse response) throws IOException, ParseException { + final String responseBody = EntityUtils.toString(response.getEntity()); - return new EasyCodefResponse(resultResponse, dataResponse); - }); - } catch (CodefException exception) { - throw exception; - } catch (Exception exception) { - throw CodefException.of(CodefError.INTERNAL_SERVER_ERROR, exception); + return switch (response.getCode()) { + case HttpStatus.SC_OK -> JSON.parseObject(responseBody).getString(ACCESS_TOKEN); + case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, responseBody); + default -> throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, responseBody); + }; + } + + EasyCodefResponse handleProductResponse(ClassicHttpResponse response) throws IOException, ParseException { + String httpResponse = EntityUtils.toString(response.getEntity()); + String decodedResponse = URLDecoder.decode(httpResponse, StandardCharsets.UTF_8); + + if (response.getCode() != HttpStatus.SC_OK) { + throw CodefException.of(CodefError.CODEF_API_SERVER_ERROR, decodedResponse); } + + return parseProductResponse(decodedResponse); } -} + + private EasyCodefResponse parseProductResponse(String decodedResponse) { + JSONObject jsonResponseObject = JSON.parseObject(decodedResponse); + + EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject(RESULT) + .to(EasyCodefResponse.Result.class); + + Object dataResponse = jsonResponseObject.getJSONObject(DATA) + .to(Object.class); + + return new EasyCodefResponse(resultResponse, dataResponse); + } +} \ 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 47ff82c..26de38d 100644 --- a/src/main/java/io/codef/api/EasyCodefToken.java +++ b/src/main/java/io/codef/api/EasyCodefToken.java @@ -14,13 +14,13 @@ protected EasyCodefToken(EasyCodefBuilder builder) { String combinedKey = String.join(DELIMITER, builder.getClientId().toString(), builder.getClientSecret().toString()); this.oauthToken = Base64.getEncoder().encodeToString(combinedKey.getBytes()); - this.accessToken = EasyCodefConnector.issueToken(oauthToken); + 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.issueToken(oauthToken); + this.accessToken = EasyCodefConnector.requestToken(oauthToken); this.expiresAt = LocalDateTime.now().plusDays(7); } return this; diff --git a/src/main/java/io/codef/api/constants/CodefReferenceUrl.java b/src/main/java/io/codef/api/constants/CodefReferenceUrl.java index 483c302..64f764c 100644 --- a/src/main/java/io/codef/api/constants/CodefReferenceUrl.java +++ b/src/main/java/io/codef/api/constants/CodefReferenceUrl.java @@ -4,7 +4,8 @@ public enum CodefReferenceUrl { KEY("https://codef.io/account/keys"), 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"); + PRODUCT("https://developer.codef.io/product/api"), + TECH_INQUIRY("https://codef.io/cs/inquiry"); private final String url; diff --git a/src/main/java/io/codef/api/dto/EasyCodefRequest.java b/src/main/java/io/codef/api/dto/EasyCodefRequest.java index c7b0d1e..b480531 100644 --- a/src/main/java/io/codef/api/dto/EasyCodefRequest.java +++ b/src/main/java/io/codef/api/dto/EasyCodefRequest.java @@ -7,7 +7,7 @@ public record EasyCodefRequest( String path, - HashMap requestParams + HashMap requestBody ) { /** * Header Format Constants diff --git a/src/main/java/io/codef/api/error/CodefError.java b/src/main/java/io/codef/api/error/CodefError.java index ce826a8..c0b7639 100644 --- a/src/main/java/io/codef/api/error/CodefError.java +++ b/src/main/java/io/codef/api/error/CodefError.java @@ -74,6 +74,14 @@ public enum CodefError { INTERNAL_SERVER_ERROR( "An error occurred on your request.", CodefReferenceUrl.DEV_GUIDE_REST_API + ), + PARSE_ERROR( + "An exception occurred because the client could not parse the server response in the expected format, possibly due to incorrect headers or body format.", + CodefReferenceUrl.TECH_INQUIRY + ), + IO_ERROR( + "An error occurred because the request was either not sent properly or not received. Please check if the outbound port to IP: 211.55.34.5, PORT: 443 is open.", + CodefReferenceUrl.TECH_INQUIRY ); private final String message; @@ -92,4 +100,4 @@ public enum CodefError { public String getMessage() { return String.format(MESSAGE_FORMAT, message, referenceUrl.getUrl()); } -} \ No newline at end of file + } \ No newline at end of file diff --git a/src/main/java/io/codef/api/util/HttpClientUtil.java b/src/main/java/io/codef/api/util/HttpClientUtil.java new file mode 100644 index 0000000..a7acd7c --- /dev/null +++ b/src/main/java/io/codef/api/util/HttpClientUtil.java @@ -0,0 +1,13 @@ +package io.codef.api.util; + +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; + +public final class HttpClientUtil { + private HttpClientUtil() { + } + + public static CloseableHttpClient createClient() { + return HttpClientBuilder.create().build(); + } +} diff --git a/src/main/java/io/codef/api/util/RsaUtil.java b/src/main/java/io/codef/api/util/RsaUtil.java index 36b4502..a26a3a8 100644 --- a/src/main/java/io/codef/api/util/RsaUtil.java +++ b/src/main/java/io/codef/api/util/RsaUtil.java @@ -10,6 +10,9 @@ import java.util.Base64; public class RsaUtil { + private RsaUtil() { + } + public static String encryptRSA(String plainText, PublicKey publicKey) { try { Cipher cipher = initializeCipher(publicKey); From 30966fabef9a262fb5ff419d4701c8137c8de38b Mon Sep 17 00:00:00 2001 From: h-beeen Date: Thu, 14 Nov 2024 10:01:54 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20httpClient5=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EB=B6=80=20=EB=A1=9C=EC=A7=81=20=EA=B0=81=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EB=8B=A8=EC=9C=84=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/codef/api/EasyCodefConnector.java | 70 +++--------------- .../java/io/codef/api/ResponseHandler.java | 72 +++++++++++++++++++ .../java/io/codef/api/ResponseProcessor.java | 9 +++ 3 files changed, 91 insertions(+), 60 deletions(-) create mode 100644 src/main/java/io/codef/api/ResponseHandler.java create mode 100644 src/main/java/io/codef/api/ResponseProcessor.java diff --git a/src/main/java/io/codef/api/EasyCodefConnector.java b/src/main/java/io/codef/api/EasyCodefConnector.java index b9359db..83bfddb 100644 --- a/src/main/java/io/codef/api/EasyCodefConnector.java +++ b/src/main/java/io/codef/api/EasyCodefConnector.java @@ -1,8 +1,6 @@ package io.codef.api; - import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONObject; import io.codef.api.constants.CodefHost; import io.codef.api.constants.CodefPath; import io.codef.api.dto.EasyCodefRequest; @@ -12,26 +10,14 @@ import io.codef.api.util.HttpClientUtil; 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.HttpStatus; -import org.apache.hc.core5.http.ParseException; -import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.io.entity.StringEntity; -import java.io.IOException; -import java.net.URLDecoder; import java.nio.charset.StandardCharsets; -import static io.codef.api.dto.EasyCodefRequest.*; -import static io.codef.api.dto.EasyCodefResponse.DATA; -import static io.codef.api.dto.EasyCodefResponse.RESULT; +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; -@FunctionalInterface -interface ResponseProcessor { - T process(ClassicHttpResponse response) throws IOException, ParseException; -} - public final class EasyCodefConnector { private static final ResponseHandler responseHandler = new ResponseHandler(); @@ -39,22 +25,18 @@ private EasyCodefConnector() { throw new IllegalStateException("Utility class"); } - /** - * OAuth 토큰을 발급받습니다. - */ - public static String requestToken(String codefOAuthToken) { + public static String requestToken( + String codefOAuthToken + ) throws CodefException { HttpPost request = createTokenRequest(codefOAuthToken); return executeRequest(request, responseHandler::handleTokenResponse); } - /** - * CODEF 상품 API를 호출합니다. - */ public static EasyCodefResponse requestProduct( EasyCodefRequest request, EasyCodefToken token, String requestUrl - ) { + ) throws CodefException { HttpPost httpRequest = createProductRequest(request, token, requestUrl); return executeRequest(httpRequest, responseHandler::handleProductResponse); } @@ -79,7 +61,10 @@ private static HttpPost createProductRequest( return httpPost; } - private static T executeRequest(HttpPost request, ResponseProcessor processor) { + private static T executeRequest( + HttpPost request, + ResponseProcessor processor + ) { try (CloseableHttpClient httpClient = HttpClientUtil.createClient()) { return httpClient.execute(request, processor::process); } catch (CodefException e) { @@ -88,39 +73,4 @@ private static T executeRequest(HttpPost request, ResponseProcessor proce throw CodefException.of(CodefError.INTERNAL_SERVER_ERROR, e); } } -} - -final class ResponseHandler { - String handleTokenResponse(ClassicHttpResponse response) throws IOException, ParseException { - final String responseBody = EntityUtils.toString(response.getEntity()); - - return switch (response.getCode()) { - case HttpStatus.SC_OK -> JSON.parseObject(responseBody).getString(ACCESS_TOKEN); - case HttpStatus.SC_UNAUTHORIZED -> throw CodefException.of(CodefError.OAUTH_UNAUTHORIZED, responseBody); - default -> throw CodefException.of(CodefError.OAUTH_INTERNAL_ERROR, responseBody); - }; - } - - EasyCodefResponse handleProductResponse(ClassicHttpResponse response) throws IOException, ParseException { - String httpResponse = EntityUtils.toString(response.getEntity()); - String decodedResponse = URLDecoder.decode(httpResponse, StandardCharsets.UTF_8); - - if (response.getCode() != HttpStatus.SC_OK) { - throw CodefException.of(CodefError.CODEF_API_SERVER_ERROR, decodedResponse); - } - - return parseProductResponse(decodedResponse); - } - - private EasyCodefResponse parseProductResponse(String decodedResponse) { - JSONObject jsonResponseObject = JSON.parseObject(decodedResponse); - - EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject(RESULT) - .to(EasyCodefResponse.Result.class); - - Object dataResponse = jsonResponseObject.getJSONObject(DATA) - .to(Object.class); - - return new EasyCodefResponse(resultResponse, dataResponse); - } } \ No newline at end of file diff --git a/src/main/java/io/codef/api/ResponseHandler.java b/src/main/java/io/codef/api/ResponseHandler.java new file mode 100644 index 0000000..4db0047 --- /dev/null +++ b/src/main/java/io/codef/api/ResponseHandler.java @@ -0,0 +1,72 @@ +package io.codef.api; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import io.codef.api.dto.EasyCodefResponse; +import io.codef.api.error.CodefError; +import io.codef.api.error.CodefException; +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; + +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +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 { + public ResponseHandler() { + } + + public String handleTokenResponse(ClassicHttpResponse response) { + try { + final String responseBody = EntityUtils.toString(response.getEntity()); + + return switch (response.getCode()) { + case HttpStatus.SC_OK -> JSON.parseObject(responseBody).getString(ACCESS_TOKEN); + 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); + } + } + + public EasyCodefResponse handleProductResponse( + ClassicHttpResponse response + ) throws CodefException { + try { + final String httpResponse = EntityUtils.toString(response.getEntity()); + final int httpStatusCode = response.getCode(); + + return switch (httpStatusCode) { + case HttpStatus.SC_OK -> { + final String 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) { + throw CodefException.of(CodefError.PARSE_ERROR, exception); + } + } + + private EasyCodefResponse parseProductResponse(String decodedResponse) { + JSONObject jsonResponseObject = JSON.parseObject(decodedResponse); + + EasyCodefResponse.Result resultResponse = jsonResponseObject.getJSONObject(RESULT).to(EasyCodefResponse.Result.class); + + Object dataResponse = jsonResponseObject.getJSONObject(DATA).to(Object.class); + + return new EasyCodefResponse(resultResponse, dataResponse); + } +} \ 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 new file mode 100644 index 0000000..f30a00e --- /dev/null +++ b/src/main/java/io/codef/api/ResponseProcessor.java @@ -0,0 +1,9 @@ +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 From 1eea427dfe70c7c0ae9da0c525edff9223acccd8 Mon Sep 17 00:00:00 2001 From: h-beeen Date: Thu, 14 Nov 2024 10:02:12 +0900 Subject: [PATCH 5/5] =?UTF-8?q?chore:=20github=20action=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=20JDK=20initialize=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cbcd32e..0e6fb90 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ jobs: publish: runs-on: ubuntu-latest if: | - github.event.workflow_run.conclusion == 'success' &&github.event.workflow_run.head_branch == 'master' + github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'master' steps: - name: Checkout code @@ -22,12 +22,6 @@ jobs: fetch-depth: 0 ref: master - - name: Set up JDK - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -68,4 +62,4 @@ jobs: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} run: | - ./gradlew publishAllPublicationsToMavenCentralRepository --stacktrace --debug \ No newline at end of file + ./gradlew publishAllPublicationsToMavenCentralRepository \ No newline at end of file